From 8eccf92b00c59e72d4eaf1ca3294b499137db537 Mon Sep 17 00:00:00 2001 From: Rich Braun Date: Thu, 30 Jul 2026 08:22:38 -0700 Subject: [PATCH 1/2] SYS-677 radicale helm chart - CalDAV / CardDAV server --- ansible/roles/docker_node/tasks/packages.yml | 8 +- ansible/roles/kubernetes/tasks/main.yml | 4 +- k8s/helm/radicale/.helmignore | 2 + k8s/helm/radicale/Chart.yaml | 14 ++ k8s/helm/radicale/templates/NOTES.txt | 28 +++ k8s/helm/radicale/templates/app.yaml | 15 ++ .../templates/tests/test-connection.yaml | 17 ++ k8s/helm/radicale/values.yaml | 238 ++++++++++++++++++ 8 files changed, 320 insertions(+), 6 deletions(-) create mode 100644 k8s/helm/radicale/.helmignore create mode 100644 k8s/helm/radicale/Chart.yaml create mode 100644 k8s/helm/radicale/templates/NOTES.txt create mode 100644 k8s/helm/radicale/templates/app.yaml create mode 100644 k8s/helm/radicale/templates/tests/test-connection.yaml create mode 100644 k8s/helm/radicale/values.yaml diff --git a/ansible/roles/docker_node/tasks/packages.yml b/ansible/roles/docker_node/tasks/packages.yml index dee72c40..af87540c 100644 --- a/ansible/roles/docker_node/tasks/packages.yml +++ b/ansible/roles/docker_node/tasks/packages.yml @@ -2,19 +2,19 @@ # package.yml - name: Install system packages - apt: + ansible.builtin.package: name: "{{ ubuntu_packages }}" - name: Install local additions - apt: + ansible.builtin.package: name: "{{ ubuntu_package_additions }}" - name: Python pip package - apt: + ansible.builtin.package: name: python3-pip when: ansible_facts['distribution_version'] >= '20.04' - name: Python pip package - apt: + ansible.builtin.package: name: python-pip when: ansible_facts['distribution_release'] < '20.04' diff --git a/ansible/roles/kubernetes/tasks/main.yml b/ansible/roles/kubernetes/tasks/main.yml index 8c5cf1e3..44aac112 100644 --- a/ansible/roles/kubernetes/tasks/main.yml +++ b/ansible/roles/kubernetes/tasks/main.yml @@ -12,7 +12,7 @@ - name: Install system packages apt: name: "{{ ubuntu_packages }}" - when: ansible_os_family == 'Debian' + when: ansible_facts['os_family'] == 'Debian' - name: Load kernel modules modprobe: @@ -50,7 +50,7 @@ - name: Set static hostname as FQDN command: hostnamectl --static set-hostname {{ inventory_hostname }} - when: inventory_hostname != ansible_hostname + when: inventory_hostname != ansible_facts['hostname'] changed_when: False - include_tasks: cri-dockerd.yml diff --git a/k8s/helm/radicale/.helmignore b/k8s/helm/radicale/.helmignore new file mode 100644 index 00000000..839de881 --- /dev/null +++ b/k8s/helm/radicale/.helmignore @@ -0,0 +1,2 @@ +*~ +.git diff --git a/k8s/helm/radicale/Chart.yaml b/k8s/helm/radicale/Chart.yaml new file mode 100644 index 00000000..beb05480 --- /dev/null +++ b/k8s/helm/radicale/Chart.yaml @@ -0,0 +1,14 @@ +apiVersion: v2 +name: radicale +description: Radicale CalDAV / CardDAV server +home: https://github.com/instantlinux/docker-tools +sources: +- https://github.com/instantlinux/docker-tools +- https://github.com/Kozea/Radicale +type: application +version: 0.1.0 +appVersion: "3.7.7" +dependencies: +- name: chartlib + version: 0.1.8 + repository: https://instantlinux.github.io/docker-tools diff --git a/k8s/helm/radicale/templates/NOTES.txt b/k8s/helm/radicale/templates/NOTES.txt new file mode 100644 index 00000000..62ea3f4b --- /dev/null +++ b/k8s/helm/radicale/templates/NOTES.txt @@ -0,0 +1,28 @@ +{{- if hasKey .Values "service" }} +{{- if or .Values.service.enabled (not (hasKey .Values.service "enabled")) }} +1. Get the application URL by running these commands: +{{- if hasKey .Values "ingress" }} +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "local.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "local.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "local.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "local.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/k8s/helm/radicale/templates/app.yaml b/k8s/helm/radicale/templates/app.yaml new file mode 100644 index 00000000..5a01911b --- /dev/null +++ b/k8s/helm/radicale/templates/app.yaml @@ -0,0 +1,15 @@ +{{- include "chartlib.configmap" . }} +--- +{{- include "chartlib.deployment" . }} +--- +{{- include "chartlib.hpa" . }} +--- +{{- include "chartlib.ingress" . }} +--- +{{- include "chartlib.ingresstotp" . }} +--- +{{- include "chartlib.service" . }} +--- +{{- include "chartlib.serviceaccount" . }} +--- +{{- include "chartlib.statefulset" . }} diff --git a/k8s/helm/radicale/templates/tests/test-connection.yaml b/k8s/helm/radicale/templates/tests/test-connection.yaml new file mode 100644 index 00000000..ae159a4f --- /dev/null +++ b/k8s/helm/radicale/templates/tests/test-connection.yaml @@ -0,0 +1,17 @@ +{{- if hasKey .Values "service" }} +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "local.fullname" . }}-test-connection" + labels: + {{- include "local.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "local.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never +{{- end }} diff --git a/k8s/helm/radicale/values.yaml b/k8s/helm/radicale/values.yaml new file mode 100644 index 00000000..c6101d60 --- /dev/null +++ b/k8s/helm/radicale/values.yaml @@ -0,0 +1,238 @@ +# Default values for radicale. + +# Version 3.7+ includes support for calendar sharing and birthday calendars +# in the radicale API; the UI and docs haven't kept up. To enable calendar +# sharing, override these configmap values: +# +# [rights] +# type = authenticated +# +# [sharing] +# type = files +# collection_by_map = true +# permit_create_map = true +# +# To activate the birthday calendar after launching this chart, setting +# up a user, and importing contacts: invoke these shell commands-- +# +# USER=someone +# PW=redact +# COLLECTION=contacts-2026-07-28 +# DOM=cal.example.com +# curl -u "$USER:$PW" -d "PathOrToken=/$USER/birthdays/" \ +# -d "PathMapped=/$USER/$COLLECTION/" -d "User=$USER" \ +# -d "Conversion=bday" https://$DOM/.sharing/v1/map/create +# curl -u "$USER:$PW" -d "PathOrToken=/$USER/birthdays/" \ +# https://$DOM/.sharing/v1/map/enable +# curl -u "$USER:$PW" -d "PathOrToken=/$USER/birthdays/" \ +# https://$DOM/.sharing/v1/map/unhide + +tlsHostname: cal.example.com +deployment: + containerPorts: + - containerPort: 5232 +volumeMounts: +- name: config + mountPath: /etc/radicale/config + subPath: config +- name: share + mountPath: /var/lib/radicale + subPath: radicale +- name: users + mountPath: /etc/radicale/users + subPath: users +volumes: +- name: config + configMap: + name: radicale +- name: share + hostPath: { path: /var/lib/docker/k8s-volumes/share } +- name: users + secret: + secretName: radicale + +image: + repository: ghcr.io/kozea/radicale + pullPolicy: IfNotPresent + # tag: default + +nameOverride: "" +fullnameOverride: "" + +serviceAccount: {} +service: + clusterIP: None + ports: + - { port: 80, targetPort: 5232, name: radicale } + type: ClusterIP + +ingress: + enabled: true + className: "" + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/enable-access-log: "false" + nginx.ingress.kubernetes.io/proxy-body-size: "50m" + +autoscaling: + enabled: false + +configmap: + data: + config: | + [server] + #hosts = localhost:5232 + #max_connections = 8 + #delay_on_error = 1 + #max_content_length = 100000000 + #max_resource_size = 10000000 + #timeout = 30 + #ssl = False + #certificate = /etc/ssl/radicale.cert.pem + #key = /etc/ssl/radicale.key.pem + #certificate_authority = + #protocol = (default) + #ciphersuite = (default) + #script_name = (default taken from HTTP_X_SCRIPT_NAME or SCRIPT_NAME) + #validate_user_value = minimal + #validate_path_value = minimal + + [encoding] + #request = utf-8 + #stock = utf-8 + + [auth] + type = htpasswd + #cache_logins = false + #cache_successful_logins_expiry = 15 + #cache_failed_logins_expiry = 90 + #ldap_uri = ldap://localhost + #ldap_base = ##BASE_DN## + #ldap_reader_dn = CN=ldapreader,CN=Users,##BASE_DN## + #ldap_secret = ldapreader-secret + #ldap_secret_file = /run/secrets/ldap_password + #ldap_filter = (&(objectClass=person)(uid={0})) + #ldap_user_attribute = cn + #ldap_security = none + #ldap_ssl_verify_mode = REQUIRED + #ldap_ssl_ca_file = + #ldap_groups_attribute = memberOf + #ldap_group_members_attribute = member + #ldap_group_base = ##GROUP_BASE_DN## + #ldap_group_filter = (objectclass=groupOfNames) + #ldap_ignore_attribute_create_modify_timestamp = false + #dovecot_connection_type = AF_UNIX + #dovecot_socket = /var/run/dovecot/auth-client + #dovecot_host = localhost + #dovecot_port = 12345 + #remote_ip_source = REMOTE_ADDR + #imap_host = localhost + #imap_security = tls + #imap_append_domain = + #oauth2_token_endpoint = + #oauth2_client_id = radicale + #oauth2_client_secret = + #pam_serivce = radicale + #pam_group_membership = + #htpasswd_filename = /etc/radicale/users + #htpasswd_encryption = autodetect + #htpasswd_cache = False + #delay = 1 + #realm = Radicale - Password Required + #lc_username = False + #strip_domain = False + #urldecode_username = False + + [rights] + #type = owner_only + #file = /etc/radicale/rights + #permit_delete_collection = True + #permit_overwrite_collection = True + + [storage] + #type = multifilesystem + #filesystem_folder = /var/lib/radicale/collections + #filesystem_cache_folder = (filesystem_folder) + #use_cache_subfolder_for_item = False + #use_cache_subfolder_for_history = False + #use_cache_subfolder_for_synctoken = False + #use_mtime_and_size_for_item_cache = False + #folder_umask = (system default, usual 0022) + #max_sync_token_age = 2592000 + #skip_broken_item = True + #strict_preconditions = False + #hook = + #predefined_collections = + + [sharing] + #type = none + #database_path = (filesystem_folder)/collection-db/sharing.csv + #database_path = (filesystem_folder)/collection-db/files + #collection_by_token = false + #collection_by_map = false + #permit_create_token = false + #permit_create_map = false + #permit_properties_overlay = false + #enforce_properties_overlay = true + #default_permissions_create_token = rp + #default_permissions_create_map = r + #conversion_bday_summary_template = [{n:f} {n:g} |{fn}|{nickname}] ({year}) (BDAY) + conversion_bday_summary_template = [{n:g} {n:f}|{fn}|{nickname}] (born {year}) + #conversion_bday_description_template = BDAY={year}-{month}-{day} + #conversion_bday_alarm_trigger_template = "" + #conversion_bday_categories = Birthday + #conversion_bday_age_max = 99 + + [web] + #type = internal + #prefer_browser_login = False + + [logging] + #level = info + #limit_content = 3000 + #trace_filter = "" + #mask_passwords = True + #bad_put_request_content = False + #backtrace_on_debug = False + #request_header_on_debug = False + #request_content_on_debug = False + #response_header_on_debug = False + #response_content_on_debug = False + #request_header_on_notice_condition = {} + #request_content_on_notice_condition = {} + #response_header_on_notice_condition = {} + #response_content_on_notice_condition = {} + #rights_rule_doesnt_match_on_debug = False + #storage_cache_actions_on_debug = False + #profiling = none + #profiling_per_request_min_duration = 3 + #profiling_per_request_header = False + #profiling_per_request_xml = False + #profiling_per_request_method_interval = 600 + #profiling_top_x_functions = 10 + + [headers] + #Access-Control-Allow-Origin = * + Content-Security-Policy = default-src 'self'; object-src 'none' + + [hook] + #type = none + #dryrun = False + #rabbitmq_endpoint = + #rabbitmq_topic = + #rabbitmq_queue_type = classic + #smtp_server = localhost + #smtp_port = 25 + #smtp_security = starttls + #smtp_ssl_verify_mode = REQUIRED + #smtp_username = + #smtp_password = + #from_email = + #mass_email = False + #new_or_added_to_event_template = + #deleted_or_removed_from_event_template = + #updated_event_template = + + [reporting] + #max_freebusy_occurrence = 10000 From 6b5bc9fe05eff7130bf09a6b1ef0095f19486510 Mon Sep 17 00:00:00 2001 From: Rich Braun Date: Thu, 30 Jul 2026 10:30:35 -0700 Subject: [PATCH 2/2] SYS-677 wip --- k8s/helm/vaultwarden/Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/k8s/helm/vaultwarden/Chart.yaml b/k8s/helm/vaultwarden/Chart.yaml index 91fc43a8..93a543df 100644 --- a/k8s/helm/vaultwarden/Chart.yaml +++ b/k8s/helm/vaultwarden/Chart.yaml @@ -5,8 +5,8 @@ home: https://github.com/instantlinux/docker-tools sources: - https://github.com/instantlinux/docker-tools type: application -version: 0.1.10 -appVersion: "1.36.0-alpine" +version: 0.1.11 +appVersion: "1.37.1-alpine" dependencies: - name: chartlib version: 0.1.8