From 9137791aace62529d535cbb941e12aa14cd576aa Mon Sep 17 00:00:00 2001 From: il Date: Sat, 28 Mar 2026 10:44:18 +0900 Subject: [PATCH] 1.7.0: Release vikunja --- ansible/inventory/group_vars/all.yaml | 1 + ansible/playbooks/app/site.yaml | 8 +++ ansible/roles/app/handlers/main.yaml | 11 ++++ .../roles/app/tasks/services/set_vikunja.yaml | 62 +++++++++++++++++++ .../infra/tasks/services/set_postgresql.yaml | 1 + config/secrets/secrets.yaml | 16 ++++- .../app/vikunja/vikunja.container.j2 | 56 +++++++++++++++++ .../auth/authelia/config/authelia.yaml.j2 | 21 +++++++ .../common/caddy/etc/app/Caddyfile.j2 | 6 ++ .../common/caddy/etc/auth/Caddyfile.j2 | 9 +++ docs/services/app/vikunja.md | 62 +++++++++++++++++++ 11 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 ansible/roles/app/tasks/services/set_vikunja.yaml create mode 100644 config/services/containers/app/vikunja/vikunja.container.j2 create mode 100644 docs/services/app/vikunja.md diff --git a/ansible/inventory/group_vars/all.yaml b/ansible/inventory/group_vars/all.yaml index 0a9d69c..be63ce5 100644 --- a/ansible/inventory/group_vars/all.yaml +++ b/ansible/inventory/group_vars/all.yaml @@ -79,3 +79,4 @@ version: immich: "v2.6.2" actualbudget: "26.3.0" paperless: "2.20.13" + vikunja: "2.2.2" diff --git a/ansible/playbooks/app/site.yaml b/ansible/playbooks/app/site.yaml index af13ab4..f0af3bf 100644 --- a/ansible/playbooks/app/site.yaml +++ b/ansible/playbooks/app/site.yaml @@ -201,6 +201,14 @@ tags: ["site", "paperless"] tags: ["site", "paperless"] + - name: Set vikunja + ansible.builtin.include_role: + name: "app" + tasks_from: "services/set_vikunja" + apply: + tags: ["site", "vikunja"] + tags: ["site", "vikunja"] + - name: Flush handlers right now ansible.builtin.meta: "flush_handlers" diff --git a/ansible/roles/app/handlers/main.yaml b/ansible/roles/app/handlers/main.yaml index f5490a4..87f7d97 100644 --- a/ansible/roles/app/handlers/main.yaml +++ b/ansible/roles/app/handlers/main.yaml @@ -64,3 +64,14 @@ changed_when: false listen: "notification_restart_paperless" ignore_errors: true # noqa: ignore-errors + +- name: Restart vikunja + ansible.builtin.systemd: + name: "vikunja.service" + state: "restarted" + enabled: true + scope: "user" + daemon_reload: true + changed_when: false + listen: "notification_restart_vikunja" + ignore_errors: true # noqa: ignore-errors diff --git a/ansible/roles/app/tasks/services/set_vikunja.yaml b/ansible/roles/app/tasks/services/set_vikunja.yaml new file mode 100644 index 0000000..2c6ff23 --- /dev/null +++ b/ansible/roles/app/tasks/services/set_vikunja.yaml @@ -0,0 +1,62 @@ +--- +- name: Set vikunja subuid + ansible.builtin.set_fact: + vikunja_subuid: "100999" + +- name: Create paperless directory + ansible.builtin.file: + path: "{{ node['home_path'] }}/{{ item }}" + state: "directory" + owner: "{{ vikunja_subuid }}" + group: "svadmins" + mode: "0770" + loop: + - "data/containers/vikunja" + - "containers/vikunja" + - "containers/vikunja/ssl" + become: true + +- name: Deploy root certificate + ansible.builtin.copy: + content: | + {{ hostvars['console']['ca']['root']['crt'] }} + dest: "{{ node['home_path'] }}/containers/vikunja/ssl/ilnmors_root_ca.crt" + owner: "{{ vikunja_subuid }}" + group: "svadmins" + mode: "0440" + become: true + notify: "notification_restart_vikunja" + 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: "VIKUNJA_SERVICE_JWTSECRET" + value: "{{ hostvars['console']['vikunja']['session_secret'] }}" + - name: "VIKUNJA_DATABASE_PASSWORD" + value: "{{ hostvars['console']['postgresql']['password']['vikunja'] }}" + - name: "VIKUNJA_AUTH_OPENID_PROVIDERS_authelia_CLIENTSECRET" + value: "{{ hostvars['console']['vikunja']['oidc']['secret'] }}" + notify: "notification_restart_vikunja" + no_log: true + +- name: Deploy vikunja.container file + ansible.builtin.template: + src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/vikunja/vikunja.container.j2" + dest: "{{ node['home_path'] }}/.config/containers/systemd/vikunja.container" + owner: "{{ ansible_user }}" + group: "svadmins" + mode: "0644" + notify: "notification_restart_vikunja" + +- name: Enable vikunja.service + ansible.builtin.systemd: + name: "vikunja.service" + state: "started" + enabled: true + daemon_reload: true + scope: "user" diff --git a/ansible/roles/infra/tasks/services/set_postgresql.yaml b/ansible/roles/infra/tasks/services/set_postgresql.yaml index 3de2afd..bdf7702 100644 --- a/ansible/roles/infra/tasks/services/set_postgresql.yaml +++ b/ansible/roles/infra/tasks/services/set_postgresql.yaml @@ -13,6 +13,7 @@ - "gitea" - "immich" - "paperless" + - "vikunja" - name: Create postgresql directory ansible.builtin.file: diff --git a/config/secrets/secrets.yaml b/config/secrets/secrets.yaml index 7b893a6..1a40252 100644 --- a/config/secrets/secrets.yaml +++ b/config/secrets/secrets.yaml @@ -117,6 +117,7 @@ postgresql: gitea: ENC[AES256_GCM,data:l+pBCzyQa3000SE9z1R4htD0V0ONsBtKy92dfgsVYsZ3XlEyVJDIBOsugwM=,iv:5t/oHW1vFAmV/s2Ze/cV9Vuqo96Qu6QvZeRbio7VX2s=,tag:4zeQaXiXIzBpy+tXsxmN7Q==,type:str] immich: ENC[AES256_GCM,data:11jvxTKA/RL0DGL6y2/X092hnDohj6yTrYGK4IVojqBd1gCOBnDvUjgmx14=,iv:oBfHxsx9nxhyKY/WOuWfybxEX2bf+lHEtsaifFRS9lg=,tag:tAfkBdgQ8ZEkLIFcDICKDw==,type:str] paperless: ENC[AES256_GCM,data:6VBrBbjVoam7SkZCSvoBTdrfkUoDghdGTiBmFLul04X/okXOHeC5zusJffY=,iv:iZumcJ3TWwZD77FzYx8THwCqC+EbnXUBrEKuPh3zgV8=,tag:u2m8SppAdxZ/duNdpuS3oQ==,type:str] + vikunja: ENC[AES256_GCM,data:/+wQdoFPTBG2elI9kZbAVWrHZ0DhMaYr4dc+2z9QNdb3TcDS2PEia0JuSAg=,iv:MViZTyUD8YqMmxSTWCQpJ30f/KQdQGOzPlRHHsQ8lAw=,tag:zov3POno139dkMxFDpj2gg==,type:str] #ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment] # # @@ -226,6 +227,17 @@ paperless: #ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment] # # +#ENC[AES256_GCM,data:V7DJHA2JQirfBsrCGhXrhg==,iv:+jYqX9hGNnuyYj9o9LpCYFVOoD6nSrtc4t40Ag0mMzo=,tag:1wSxKtkJm42reUxdwYDvlg==,type:comment] +vikunja: + session_secret: ENC[AES256_GCM,data:CMyw8JGHyTczGsrOJJwQBKfXMU4Sudvwkur1Lgx4o64=,iv:F2VmpqddiDT4jGaGDKGl6FARsQOt3lLz3X6TjC2MIVU=,tag:UJYyzrl/FX1BNwY4ROFncA==,type:str] + il: + password: ENC[AES256_GCM,data:wDYAVUTFyL/CaXQXYviP3WAILmnREwYui8PZq9nXJPNa3FlwX6b/fzxbCvw=,iv:We+jb4W62O8tYRjGPv+lwlhyVF8eIeTiPNoELdLU+6M=,tag:y7vTZ+6TAsv4XajB4JOL7A==,type:str] + oidc: + secret: ENC[AES256_GCM,data:QwqndYsfr+fh9OLkHYtLYCa6WUdhnL7A4btz1d1eelTwq3Kps5S6BUN5qZg=,iv:51N8byIAAUh4ky7YBAuEJOBEWu1d9AX5W1m37/cLlCM=,tag:GD7jbxNGd748TCPgqsxyMg==,type:str] + hash: ENC[AES256_GCM,data:ORifyT4u1V2CyBCNBgF72wwS2i05mlzA4iIVEa1cH9aaE69PdiQvGGzMHK+tmlfpVaVQEENSt1QDUSSlMyeuZT/3a0JwAvlz+XDbpS7bicL2cB6DCa4JyEd/rbGRXs0/COfxPxXzYv7jq9gd2uSJ+cCGYb/93WuEXSEI6PHi+FF7N94=,iv:FVSGySa4YB2vwenqSagBzxeIexg91ewvcQMix+etmng=,tag:yyQtOgzOZypba+rV3A1K9g==,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: password: ENC[AES256_GCM,data:qu0f9L7A0eFq/UCpaRs=,iv:W8LLOp3MSfd/+EfNEZNf91K8GgI5eUfVPoWTRES2C0Y=,tag:Q5FlAOfwqwJwPvd7k6i+0g==,type:str] @@ -255,7 +267,7 @@ sops: UmliaFNxVTBqRkI1QWJpWGpTRWxETW8KEY/8AfU73UOzCGhny1cNnd5dCNv7bHXt k+uyWPPi+enFkVaceSwMFrA66uaWWrwAj11sXEB7yzvGFPrnAGezjQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-03-24T06:37:53Z" - mac: ENC[AES256_GCM,data:+by7KiDiod7d0KtLB8jBnuTUtISLkn7WrwW/MrOGnxxqO9JnmD36HeugM782K79Rgymu0osexyvSQ2xpwfDQL/6WjfKkqxXirpeVrHFjjMFrJ3r2Wnn9GoCRf3ObJEXJD8x59IL/fsTDfzGTLaOG71I5Zs7j+LQnrm4Uj3KD6Rg=,iv:lHcuCw7a7j7CkBT183fYMhpQhx97Mz4DYrWYZQYbFNQ=,tag:yAZXT4FrAbwgkespCPdIBA==,type:str] + lastmodified: "2026-03-28T01:12:44Z" + mac: ENC[AES256_GCM,data:hGvVNYQ2qUf9zGMbY5lupY9NI4rCnbza7OruAho12UTcTpD9vktWj559U/UoZpKDknR9n+d50UOGcukeFgLtPBo6xy0+Yf0FRCZU59SQ9k02f3wyIck5ANikBImeN6gGKUbiuIcQUqD3jWH1b2u6V02KN8UkKs7CywHKhI2IoKg=,iv:orXjewDUalAxGOdMjXs18Al98MLzYHTyaWnCz0VqRU4=,tag:hZ5aV3E2kB0US4zAk66Z2Q==,type:str] unencrypted_suffix: _unencrypted version: 3.12.1 diff --git a/config/services/containers/app/vikunja/vikunja.container.j2 b/config/services/containers/app/vikunja/vikunja.container.j2 new file mode 100644 index 0000000..df326fa --- /dev/null +++ b/config/services/containers/app/vikunja/vikunja.container.j2 @@ -0,0 +1,56 @@ +[Quadlet] +DefaultDependencies=false + +[Unit] +Description=Vikunja + +After=network-online.target +Wants=network-online.target + +[Container] +Image=docker.io/vikunja/vikunja:{{ version['containers']['vikunja'] }} +ContainerName=vikunja +HostName=vikunja +PublishPort=3456:3456/tcp + +# Volumes +Volume=%h/data/containers/vikunja:/app/vikunja/files:rw +Volume=%h/containers/vikunja/ssl:/etc/ssl/vikunja:ro + +# General +Environment="TZ=Asia/Seoul" +Environment="VIKUNJA_DEFAULTSETTINGS_TIMEZONE=Asia/Seoul" +Environment="VIKUNJA_SERVICE_TIMEZONE=Asia/Seoul" +Environment="VIKUNJA_SERVICE_PUBLICURL=https://vikunja.ilnmors.com" +Environment="VIKUNJA_SERVICE_ENABLEREGISTRATION=false" +Secret=VIKUNJA_SERVICE_JWTSECRET,type=env + + +# Database +Environment="VIKUNJA_DATABASE_TYPE=postgres" +Environment="VIKUNJA_DATABASE_HOST={{ infra_uri['postgresql']['domain'] }}" +Environment="VIKUNJA_DATABASE_USER=vikunja" +Environment="VIKUNJA_DATABASE_DATABASE=vikunja_db" +Environment="VIKUNJA_DATABASE_SSLMODE=verify-full" +Environment="VIKUNJA_DATABASE_SSLROOTCERT=/etc/ssl/vikunja/ilnmors_root_ca.crt" +Secret=VIKUNJA_DATABASE_PASSWORD,type=env + + +# OIDC +Environment="VIKUNJA_AUTH_OPENID_ENABLED=true" +Environment="VIKUNJA_AUTH_OPENID_PROVIDERS_authelia_NAME=Authelia" +Environment="VIKUNJA_AUTH_OPENID_PROVIDERS_authelia_AUTHURL=https://authelia.ilnmors.com" +Environment="VIKUNJA_AUTH_OPENID_PROVIDERS_authelia_CLIENTID=vikunja" +# Environment="VIKUNJA_AUTH_OPENID_PROVIDERS_authelia_SCOPE=" default value = openid email profile +Environment="VIKUNJA_AUTH_OPENID_PROVIDERS_authelia_USERNAMEFALLBACK=true" +Environment="VIKUNJA_AUTH_OPENID_PROVIDERS_authelia_EMAILFALLBACK=true" +Secret=VIKUNJA_AUTH_OPENID_PROVIDERS_authelia_CLIENTSECRET,type=env + +[Service] +ExecStartPre=/usr/bin/nc -zv {{ infra_uri['postgresql']['domain'] }} {{ infra_uri['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 df44cb1..84b6acd 100644 --- a/config/services/containers/auth/authelia/config/authelia.yaml.j2 +++ b/config/services/containers/auth/authelia/config/authelia.yaml.j2 @@ -219,3 +219,24 @@ identity_providers: access_token_signed_response_alg: 'none' userinfo_signed_response_alg: 'none' token_endpoint_auth_method: 'client_secret_post' + # https://www.authelia.com/integration/openid-connect/clients/vikunja/ + - client_id: 'vikunja' + client_name: 'Vikunja' + client_secret: '{{ hostvars['console']['vikunja']['oidc']['hash'] }}' + public: false + authorization_policy: 'one_factor' + require_pkce: false + pkce_challenge_method: '' + redirect_uris: + - 'https://vikunja.ilnmors.com/auth/openid/authelia' + 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' diff --git a/config/services/containers/common/caddy/etc/app/Caddyfile.j2 b/config/services/containers/common/caddy/etc/app/Caddyfile.j2 index 83b4116..2217feb 100644 --- a/config/services/containers/common/caddy/etc/app/Caddyfile.j2 +++ b/config/services/containers/common/caddy/etc/app/Caddyfile.j2 @@ -59,3 +59,9 @@ paperless.app.ilnmors.internal { header_up Host {http.request.header.X-Forwarded-Host} } } +vikunja.app.ilnmors.internal { + import private_tls + reverse_proxy host.containers.internal:3456 { + header_up Host {http.request.header.X-Forwarded-Host} + } +} diff --git a/config/services/containers/common/caddy/etc/auth/Caddyfile.j2 b/config/services/containers/common/caddy/etc/auth/Caddyfile.j2 index 8521c6a..cea2ef1 100644 --- a/config/services/containers/common/caddy/etc/auth/Caddyfile.j2 +++ b/config/services/containers/common/caddy/etc/auth/Caddyfile.j2 @@ -109,6 +109,15 @@ paperless.ilnmors.com { } } } +vikunja.ilnmors.com { + import crowdsec_log + route { + crowdsec + reverse_proxy https://vikunja.app.ilnmors.internal { + header_up HOST {http.reverse_proxy.upstream.host} + } + } +} # Internal domain auth.ilnmors.internal { diff --git a/docs/services/app/vikunja.md b/docs/services/app/vikunja.md new file mode 100644 index 0000000..5a7c027 --- /dev/null +++ b/docs/services/app/vikunja.md @@ -0,0 +1,62 @@ +# vikunja + +## Prerequisite + +### Create database + +- Create the password with `openssl rand -base64 32` + - Save this value in secrets.yaml in `postgresql.password.vikunja` + - Access infra server to create vikunja_db with `podman exec -it postgresql psql -U postgres` + +```SQL +CREATE USER vikunja WITH PASSWORD 'postgresql.password.vikunja'; +CREATE DATABASE vikunja_db; +ALTER DATABASE vikunja_db OWNER TO vikunja; +``` + +### Create oidc secret and hash + +- Create the secret with `openssl rand -base64 32` +- access to auth vm + - `podman exec -it authelia sh` + - `authelia crypto hash generate pbkdf2 --password 'vikunja.oidc.secret'` +- Save this value in secrets.yaml in `vikunja.oidc.secret` and `vikunja.oidc.hash` + +### Create session secret value + +- Create the secret with `LC_ALL=C tr -dc 'A-Za-z0-9!#%&()*+,-./:;<=>?@[\]^_{|}~'