From ada7e751d8d7424fc44f84c5cdaa64905c4ebcdf Mon Sep 17 00:00:00 2001 From: il Date: Fri, 21 Aug 2026 14:54:09 +0900 Subject: [PATCH] 2026-AUG-21.1 --- ansible/inventory/group_vars/all.yaml | 10 ++ ansible/playbooks/app/site.yaml | 8 ++ ansible/roles/app/handlers/main.yaml | 11 ++ .../roles/app/tasks/services/set_tandoor.yaml | 102 ++++++++++++++++++ config/secrets/secrets.yaml | 17 ++- .../app/homepage/config/services.yaml.j2 | 9 +- .../app/tandoor/tandoor.container.j2 | 58 ++++++++++ .../auth/authelia/config/authelia.yaml.j2 | 22 ++++ 8 files changed, 233 insertions(+), 4 deletions(-) create mode 100644 ansible/roles/app/tasks/services/set_tandoor.yaml create mode 100644 config/services/containers/app/tandoor/tandoor.container.j2 diff --git a/ansible/inventory/group_vars/all.yaml b/ansible/inventory/group_vars/all.yaml index 76d7483..0cd3979 100644 --- a/ansible/inventory/group_vars/all.yaml +++ b/ansible/inventory/group_vars/all.yaml @@ -291,6 +291,15 @@ services: dependency: web: "app-public" db: "postgresql" + tandoor: + domain: + public: "tandoor" + internal: "tandoor.app" + ports: + http: "8003" + dependency: + web: "app-public" + db: "postgresql" homebox: domain: public: "homebox" @@ -357,3 +366,4 @@ version: booklogr: "v1.11.1" papra: "26.6.1-rootless" romm: "5.1.0" + tandoor: "2.6.13" diff --git a/ansible/playbooks/app/site.yaml b/ansible/playbooks/app/site.yaml index 14bd43d..294d847 100644 --- a/ansible/playbooks/app/site.yaml +++ b/ansible/playbooks/app/site.yaml @@ -282,6 +282,14 @@ tags: ["site", "papra"] tags: ["site", "papra"] + - name: Set tandoor + ansible.builtin.include_role: + name: "app" + tasks_from: "services/set_tandoor" + apply: + tags: ["site", "tandoor"] + tags: ["site", "tandoor"] + - name: Set romm ansible.builtin.include_role: name: "app" diff --git a/ansible/roles/app/handlers/main.yaml b/ansible/roles/app/handlers/main.yaml index 35aefb3..3904286 100644 --- a/ansible/roles/app/handlers/main.yaml +++ b/ansible/roles/app/handlers/main.yaml @@ -197,3 +197,14 @@ changed_when: false listen: "notification_restart_romm" ignore_errors: true # noqa: ignore-errors + +- name: Restart tandoor + ansible.builtin.systemd: + name: "tandoor.service" + state: "restarted" + enabled: true + scope: "user" + daemon_reload: true + changed_when: false + listen: "notification_restart_tandoor" + ignore_errors: true # noqa: ignore-errors diff --git a/ansible/roles/app/tasks/services/set_tandoor.yaml b/ansible/roles/app/tasks/services/set_tandoor.yaml new file mode 100644 index 0000000..59f65cb --- /dev/null +++ b/ansible/roles/app/tasks/services/set_tandoor.yaml @@ -0,0 +1,102 @@ +--- +- name: Create tandoor directory + ansible.builtin.file: + path: "{{ node['home_path'] }}/{{ item }}" + state: "directory" + owner: "{{ ansible_user }}" + group: "{{ node['group'] }}" + mode: "0770" + loop: + - "data/containers/tandoor" + - "data/containers/tandoor/mediafiles" + - "containers/tandoor" + - "containers/tandoor/ssl" + +- name: Deploy root certificate + ansible.builtin.copy: + content: | + {{ hostvars['console']['ca']['root']['crt'] }} + dest: "{{ node['home_path'] }}/containers/tandoor/ssl/{{ root_cert_filename }}" + owner: "{{ ansible_user }}" + group: "{{ node['group'] }}" + mode: "0440" + notify: "notification_restart_tandoor" + no_log: true + +- name: Register secret value to podman secret + containers.podman.podman_secret: + name: "{{ item.name }}" + data: "{{ item.value }}" + state: "present" + force: true + loop: + - name: "TANDOOR_SECRET_KEY" + value: "{{ hostvars['console']['tandoor']['secret_key'] }}" + - name: "TANDOOR_POSTGRES_PASSWORD" + value: "{{ hostvars['console']['postgresql']['password']['tandoor'] }}" + - name: "TANDOOR_SOCIALACCOUNT_PROVIDERS" + value: | + { + "openid_connect": { + "SCOPE": ["openid", "profile", "email"], + "OAUTH_PKCE_ENABLED": true, + "APPS": [ + { + "provider_id": "authelia", + "name": "authelia", + "client_id": "tandoor", + "secret": "{{ hostvars['console']['tandoor']['oidc']['secret'] }}", + "settings": { + "server_url": "https://{{ services['authelia']['domain']['public'] }}.{{ domain['public'] }}/.well-known/openid-configuration", + "token_auth_method": "client_secret_basic" + } + } + ] + } + } + notify: "notification_restart_tandoor" + no_log: true + +- name: Pull image + containers.podman.podman_image: + name: "docker.io/vabene1111/recipes" + tag: "{{ version['containers']['tandoor'] }}" + state: "present" + pull: true + +- name: Deploy tandoor.container file + ansible.builtin.template: + src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/tandoor/tandoor.container.j2" + dest: "{{ node['home_path'] }}/.config/containers/systemd/tandoor.container" + owner: "{{ ansible_user }}" + group: "{{ node['group'] }}" + mode: "0644" + notify: "notification_restart_tandoor" + +- name: Enable tandoor.service + ansible.builtin.systemd: + name: "tandoor.service" + state: "started" + enabled: true + daemon_reload: true + scope: "user" + +- name: Check container archive images + ansible.builtin.stat: + path: "{{ node['home_path'] }}/archives/containers/docker.io_vabene1111_recipes_{{ version['containers']['tandoor'] }}.tar" + register: container_archive_images + +- name: Save container archive images + containers.podman.podman_save: + image: + - "docker.io/vabene1111/recipes:{{ version['containers']['tandoor'] }}" + dest: "{{ node['home_path'] }}/archives/containers/docker.io_vabene1111_recipes_{{ version['containers']['tandoor'] }}.tar" + format: "oci-archive" + force: false + when: not container_archive_images.stat.exists + +- name: Fetch container archive images + ansible.builtin.fetch: + src: "{{ node['home_path'] }}/archives/containers/docker.io_vabene1111_recipes_{{ version['containers']['tandoor'] }}.tar" + dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/" + flat: true diff --git a/config/secrets/secrets.yaml b/config/secrets/secrets.yaml index 0afb153..a342881 100644 --- a/config/secrets/secrets.yaml +++ b/config/secrets/secrets.yaml @@ -141,6 +141,7 @@ postgresql: mastodon: ENC[AES256_GCM,data:hq1ETBK9iUI7kWFO+9pZwMQMbmyLExGk7/VzBwDdLFbzptXL2KEi6oYRCS0=,iv:hF1C8lsnP2i3LxDIuOA6op4qKXQKjJ7gqznDst7hVAU=,tag:maR7Jvsv3DJGrO77EzmBDA==,type:str] booklogr: ENC[AES256_GCM,data:K+9Bb/GMbaviYIIesimugQg2RXF590L2T2XNlRI/bjc9BBb9/iivFlhLwa4=,iv:E/MGt0bKlITYKMYZjlmTZ2YonYTvNTaRI23n5xazyeE=,tag:Y3WUSHBHQiF1MVD/zQr5RQ==,type:str] romm: ENC[AES256_GCM,data:nizBKAPWPRTxKZRPGOoff6WrbpK9z3s1RDhJmKghO4BhmgguH2C/5DlQ6V0=,iv:uiscRdOoS//Mxs8Lmw6sA+/YtcVhhuJoxP7AxQGJHNA=,tag:UM89ocQFBK3zVDsS8LhAiw==,type:str] + tandoor: ENC[AES256_GCM,data:+5g/jzRMQoxocZntoE+1BU0MS00yEp0tyVNxriEIJ0pW2V1bsLhUAkwQ748=,iv:feNbpOhkdjmKS7Ovy0KGioOTyjQ4sflfH+50niP7Y8Y=,tag:51/u+EE3Pp5o7jnlj0AWIw==,type:str] #ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment] # # @@ -348,6 +349,18 @@ romm: #ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment] # # +#ENC[AES256_GCM,data:7nupd3g2GlsjuxZFFdXiyw==,iv:vdwhjZTOhHPp14Z0oGdZV9zs9IAebjrxuE7ih66mgr8=,tag:lzbMg8K/yYEuUD+Q9OgbWw==,type:comment] +tandoor: + secret_key: ENC[AES256_GCM,data:RZc91Ji9x7A2PQF+lw47B6tlczWO7e8hRfUJIlNd0BtkPIacDvztR7/VbCc=,iv:IlpZcvR70XvNc46KrrNls5RbndnWeobU0k9qQMIhA0Q=,tag:d0weqGUUFa5bH5tEDzPDYg==,type:str] + admin: + id: ENC[AES256_GCM,data:N8TQU9gwDUe5Roc=,iv:dPikeLm98iB5JMXv5xTePZcbz7bUdRUfQRUkBmU4aqQ=,tag:2EJ5N51bYi2uOhOtnVbLUA==,type:str] + password: ENC[AES256_GCM,data:888/eGiNJVc7HppIBnefmEpZXcmZntKtJgB3Gigvld44kubx9n9exWQ1JPQ=,iv:eebNRxHJScFtoEcp4QEKWH0k1oQRKb1tGvRD9B3KQmw=,tag:LGi/nxTSRi/5Zqm4po9KFQ==,type:str] + oidc: + secret: ENC[AES256_GCM,data:w3Md+8XEJ+VMP9WGcyA3o/Hy55xvMDSyOM+PxUBPnXen/IFkTYoBRnyAxtc=,iv:5ZfNmM/fBEaSpw8SD7gXph1czdXOYvBrr19MRaPjYRE=,tag:MeuWc/KuSUyIeavakjqIbw==,type:str] + hash: ENC[AES256_GCM,data:IsEmqlacmA+SO4QkobJBMDnaEF19/EWnzhKUgjekhF02E4teOKEI5DRosoe3AFz5sB9qg+1DO6y6nvHDaTP5WKGJcaonu3UOO6jMPOAHudJsTrOb+6cM4govT9VITnF0RbwImqrwOutzQe0IRyGDxZ7TkZTmdlwCs9DiLfyf918nMgg=,iv:d/Kb5tPeYVIbYQxWBwVZ485WCZfTxwPabx4+NLMBP2M=,tag:i9VtcCeoL5r11Q7F/Secpw==,type:str] +#ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment] +# +# #ENC[AES256_GCM,data:T4Wtn49AAxPd2QUFTR+q,iv:bH5goGWBDqumAat9dUv2OwfCUJUpuVqncTMqMBZUXhI=,tag:G+W6hHA+yftQ+4RJpXrxHg==,type:comment] switch: admin: @@ -364,7 +377,7 @@ sops: k+uyWPPi+enFkVaceSwMFrA66uaWWrwAj11sXEB7yzvGFPrnAGezjQ== -----END AGE ENCRYPTED FILE----- recipient: age120wuwcmsm845ztsvsz46pswj5je53uc2n35vadklrfqudu6cxuusxetk7y - lastmodified: "2026-07-08T07:31:10Z" - mac: ENC[AES256_GCM,data:QjNInc+F1ZpvXP1LHlfJ6JgVpNcZHEX0072glJKAHAxx1DmRrgnHATFHBIIuPM6Y4WM+fLewrnu9Mlp7L7KKM5NKeq7aryEaT6cfoN69YG/pOpq2FlXA3WIBcdja7VA17dYeqJz8z7sN5qHTq2mY1KG1Z5ogg+F64TIOpYRFLNI=,iv:iTKy/SAaY66GnYxc0PXyDQH3C57fbi+DiMOkKBgPpjA=,tag:e+IpJ1hksr0HvvIpJo2tzA==,type:str] + lastmodified: "2026-08-21T05:32:45Z" + mac: ENC[AES256_GCM,data:VED6JYuDQlmXl6buMuPEkF4LRbVL8RmlQhNYguHRvrUEbAYZn4iB5bUCoZuLhklsBc5b9Y+Xzgd4Gixk0puS85K2FxQ1jXazq/pVvKl3UPn3XFbqhXFtwLxq1Rqj1iY69E+pgiI31/pflcGwmJbzOFNO8SZ2jMTrH690P3XsCAM=,iv:6HR/+VC2Or+RMMdrHD7r6sp4oUR4g/MsNudg+J7iPe0=,tag:DrH6lcpptXn1OOucxfzK/g==,type:str] unencrypted_suffix: _unencrypted version: 3.13.2 diff --git a/config/services/containers/app/homepage/config/services.yaml.j2 b/config/services/containers/app/homepage/config/services.yaml.j2 index 8cd1c1e..cf86b5b 100644 --- a/config/services/containers/app/homepage/config/services.yaml.j2 +++ b/config/services/containers/app/homepage/config/services.yaml.j2 @@ -1,12 +1,17 @@ +{% set icon_map = { + 'tandoor': 'tandoor-recipes', + 'ldap': 'lldap' +} %} + - Services: {% for item in (homepage_public_services | reject('in', ['homepage', 'collabora'])) %} - {{ item }}: - icon: {{ item }}.svg + icon: {{ icon_map.get(item, item) }}.svg href: https://{{ services[item]['domain']['public'] }}.{{ domain['public'] }} {% endfor %} - Admin: {% for item in homepage_internal_services %} - {{ item }}: - icon: {{ 'lldap' if item == 'ldap' else item }}.svg + icon: {{ icon_map.get(item, item) }}.svg href: https://{{ services[item]['domain']['internal'] }}.{{ domain['internal'] }} {% endfor %} \ No newline at end of file diff --git a/config/services/containers/app/tandoor/tandoor.container.j2 b/config/services/containers/app/tandoor/tandoor.container.j2 new file mode 100644 index 0000000..813ae66 --- /dev/null +++ b/config/services/containers/app/tandoor/tandoor.container.j2 @@ -0,0 +1,58 @@ +[Quadlet] +DefaultDependencies=false + +[Unit] +Description=Tandoor Recipes + +[Container] +Image=docker.io/vabene1111/recipes:{{ version['containers']['tandoor'] }} + +ContainerName=tandoor +HostName=tandoor + +PublishPort={{ services['tandoor']['ports']['http'] }}:80/tcp + +# Volumes +Volume=%h/data/containers/tandoor/mediafiles:/opt/recipes/mediafiles:rw +Volume=%h/containers/tandoor/ssl:/etc/ssl/tandoor:ro + +# General +Environment="TZ={{ timezone }}" +Environment="ALLOWED_HOSTS={{ services['tandoor']['domain']['public'] }}.{{ domain['public'] }}" +Environment="CSRF_TRUSTED_ORIGINS=https://{{ services['tandoor']['domain']['public'] }}.{{ domain['public'] }}" +# auth Caddy -> app Caddy -> Tandoor built-in nginx +Environment="ALLAUTH_TRUSTED_PROXY_COUNT=3" + +# Secrets +Secret=TANDOOR_SECRET_KEY,type=env,target=SECRET_KEY + +# Database (PostgreSQL) +Environment="DB_ENGINE=django.db.backends.postgresql" +Environment="POSTGRES_HOST={{ services['postgresql']['domain']['internal'] }}.{{ domain['internal'] }}" +Environment="POSTGRES_PORT={{ services['postgresql']['ports']['tcp'] }}" +Environment="POSTGRES_USER=tandoor" +Environment="POSTGRES_DB=tandoor_db" +Environment="PGSSLMODE=verify-full" +Environment="PGSSLROOTCERT=/etc/ssl/tandoor/{{ root_cert_filename }}" +Secret=TANDOOR_POSTGRES_PASSWORD,type=env,target=POSTGRES_PASSWORD + +# Login +Environment="HIDE_LOGIN_FORM=1" +Environment="ENABLE_SIGNUP=0" + +# OIDC +Environment="SOCIAL_PROVIDERS=allauth.socialaccount.providers.openid_connect" +Environment="SOCIALACCOUNT_AUTO_SIGNUP=1" +Environment="SOCIALACCOUNT_LOGIN_ON_GET=1" +Environment="SOCIAL_DEFAULT_ACCESS=1" +Environment="SOCIAL_DEFAULT_GROUP=user" +Secret=TANDOOR_SOCIALACCOUNT_PROVIDERS,type=env,target=SOCIALACCOUNT_PROVIDERS + +[Service] +ExecStartPre=/usr/bin/nc -zv {{ services['postgresql']['domain']['internal'] }}.{{ domain['internal'] }} {{ services['postgresql']['ports']['tcp'] }} +Restart=always +RestartSec=10s +TimeoutStopSec=120 + +[Install] +WantedBy=default.target diff --git a/config/services/containers/auth/authelia/config/authelia.yaml.j2 b/config/services/containers/auth/authelia/config/authelia.yaml.j2 index a9dad25..a9cec0c 100644 --- a/config/services/containers/auth/authelia/config/authelia.yaml.j2 +++ b/config/services/containers/auth/authelia/config/authelia.yaml.j2 @@ -407,3 +407,25 @@ identity_providers: access_token_signed_response_alg: 'none' userinfo_signed_response_alg: 'none' token_endpoint_auth_method: 'client_secret_basic' + # https://www.authelia.com/integration/openid-connect/clients/tandoor/ + - client_id: 'tandoor' + client_name: 'tandoor' + consent_mode: 'implicit' + client_secret: '$pbkdf2-sha512$310000$IOvwfqSgUvMZ5nxZ.dEDTw$FX3x1XaH3.S9gNFHiv0DdcCSCEomFMdoxd.yA3PEmVksRyd7D8LuSdooN.DA3B.jyVDVLFSJ9isIChEbJSmEvw' + public: false + authorization_policy: 'one_factor' + require_pkce: false + pkce_challenge_method: '' + redirect_uris: + - 'https://{{ services['tandoor']['domain']['public'] }}.{{ domain['public'] }}/accounts/oidc/authelia/login/callback/' + scopes: + - 'openid' + - 'profile' + - 'email' + response_types: + - 'code' + grant_types: + - 'authorization_code' + access_token_signed_response_alg: 'none' + userinfo_signed_response_alg: 'none' + token_endpoint_auth_method: 'client_secret_basic'