feat(papra, romm): add feature

This commit is contained in:
il
2026-07-09 12:22:54 +09:00
parent e0df8cfba5
commit 3a38271969
38 changed files with 1372 additions and 111 deletions
+44 -19
View File
@@ -184,10 +184,12 @@ services:
public: "mastodon"
internal: "mastodon.app"
ports:
http: "3006"
streaming: "4000"
redis: "6385"
http: "3002"
api: "4000"
redis: "6379"
subuid: "100990"
route:
api: "/api/v1/streaming*"
dependency:
web: "app-public"
db: "postgresql"
@@ -196,8 +198,8 @@ services:
public: "nextcloud"
internal: "nextcloud.app"
ports:
http: "8002"
redis: "6382"
http: "8001"
redis: "6380"
subuid: "100032"
dependency:
web: "app-public"
@@ -217,20 +219,33 @@ services:
internal: "immich.app"
ports:
http: "2283"
redis: "6379"
redis: "6381"
dependency:
web: "app-public"
db: "postgresql"
immich-ml:
ports:
http: "3003"
booklogr:
domain:
public: "booklogr"
internal: "booklogr.app"
ports:
http: "5150"
api: "5000"
route:
api: "/v1 /v1/*"
deny: "/v1/login /v1/login/* /v1/register /v1/register/*"
dependency:
web: "app-public"
db: "postgresql"
outline:
domain:
public: "outline"
internal: "outline.app"
ports:
http: "3004"
redis: "6384"
redis: "6382"
subuid: "101000"
dependency:
web: "app-public"
@@ -250,29 +265,27 @@ services:
public: "sure"
internal: "sure.app"
ports:
http: "3002"
http: "3006"
redis: "6383"
subuid: "100999"
dependency:
web: "app-public"
db: "postgresql"
paperless:
papra:
domain:
public: "paperless"
internal: "paperless.app"
public: "papra"
internal: "papra.app"
ports:
http: "8001"
redis: "6380"
subuid: "100999"
http: "1221"
subuid: "100998"
dependency:
web: "app-public"
db: "postgresql"
mealie:
domain:
public: "mealie"
internal: "mealie.app"
ports:
http: "9001"
http: "9000"
subuid: "100910"
dependency:
web: "app-public"
@@ -286,6 +299,18 @@ services:
dependency:
web: "app-public"
db: "postgresql"
romm:
domain:
public: "romm"
internal: "romm.app"
ports:
http: "8002"
redis: "6384"
subuid: "100999"
dependency:
web: "app-public"
db: "postgresql"
version:
packages:
@@ -303,8 +328,6 @@ version:
postgresql: "18.4"
### For immich - https://github.com/immich-app/base-images/blob/main/postgres/versions.yaml
vectorchord: "1.1.1"
### For bonfire
postgis: "3"
## lldap
ldap: "v0.6.3"
## maddy
@@ -320,7 +343,6 @@ version:
gitea: "1.26.4"
redis: "8.8.0"
immich: "v3.0.1"
paperless: "2.20.15"
nextcloud: "34.0.1"
collabora: "26.04.2.1.1"
sure: "0.7.2"
@@ -331,3 +353,6 @@ version:
homepage: "v1.13.2"
snappymail: "v2.38.2"
mastodon: "v4.6.3"
booklogr: "v1.11.1"
papra: "26.6.1-rootless"
romm: "5.0.0-beta.2"
+24 -8
View File
@@ -202,14 +202,6 @@
tags: ["site", "immich"]
tags: ["site", "immich"]
- name: Set paperless
ansible.builtin.include_role:
name: "app"
tasks_from: "services/set_paperless"
apply:
tags: ["site", "paperless"]
tags: ["site", "paperless"]
- name: Set nextcloud
ansible.builtin.include_role:
name: "app"
@@ -274,6 +266,30 @@
tags: ["site", "mastodon"]
tags: ["site", "mastodon"]
- name: Set booklogr
ansible.builtin.include_role:
name: "app"
tasks_from: "services/set_booklogr"
apply:
tags: ["site", "booklogr"]
tags: ["site", "booklogr"]
- name: Set papra
ansible.builtin.include_role:
name: "app"
tasks_from: "services/set_papra"
apply:
tags: ["site", "papra"]
tags: ["site", "papra"]
- name: Set romm
ansible.builtin.include_role:
name: "app"
tasks_from: "services/set_romm"
apply:
tags: ["site", "romm"]
tags: ["site", "romm"]
- name: Set homepage
ansible.builtin.include_role:
name: "app"
+36 -11
View File
@@ -43,17 +43,6 @@
listen: "notification_restart_immich-ml"
ignore_errors: true # noqa: ignore-errors
- name: Restart paperless
ansible.builtin.systemd:
name: "paperless.service"
state: "restarted"
enabled: true
scope: "user"
daemon_reload: true
changed_when: false
listen: "notification_restart_paperless"
ignore_errors: true # noqa: ignore-errors
- name: Restart nextcloud
ansible.builtin.systemd:
name: "nextcloud.service"
@@ -172,3 +161,39 @@
changed_when: false
listen: "notification_restart_mastodon"
ignore_errors: true # noqa: ignore-errors
- name: Restart booklogr
ansible.builtin.systemd:
name: "{{ item }}"
state: "restarted"
enabled: true
scope: "user"
daemon_reload: true
loop:
- "booklogr-api.service"
- "booklogr-web.service"
changed_when: false
listen: "notification_restart_booklogr"
ignore_errors: true # noqa: ignore-errors
- name: Restart papra
ansible.builtin.systemd:
name: "papra.service"
state: "restarted"
enabled: true
scope: "user"
daemon_reload: true
changed_when: false
listen: "notification_restart_papra"
ignore_errors: true # noqa: ignore-errors
- name: Restart romm
ansible.builtin.systemd:
name: "romm.service"
state: "restarted"
enabled: true
scope: "user"
daemon_reload: true
changed_when: false
listen: "notification_restart_romm"
ignore_errors: true # noqa: ignore-errors
@@ -0,0 +1,112 @@
---
- name: Create booklogr directory
ansible.builtin.file:
path: "{{ node['home_path'] }}/{{ item }}"
state: "directory"
owner: "{{ ansible_user }}"
group: "{{ node['group'] }}"
mode: "0770"
loop:
- "data/containers/booklogr"
- "data/containers/booklogr/profile_pictures"
- "data/containers/booklogr/export_data"
- "containers/booklogr"
- "containers/booklogr/ssl"
become: true
- name: Deploy root certificate
ansible.builtin.copy:
content: |
{{ hostvars['console']['ca']['root']['crt'] }}
dest: "{{ node['home_path'] }}/containers/booklogr/ssl/{{ root_cert_filename }}"
owner: "{{ ansible_user }}"
group: "{{ node['group'] }}"
mode: "0440"
become: true
notify: "notification_restart_booklogr"
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: "BOOKLOGR_AUTH_SECRET_KEY"
value: "{{ hostvars['console']['booklogr']['secret_key'] }}"
- name: "BOOKLOGR_POSTGRES_PASSWORD"
value: "{{ hostvars['console']['postgresql']['password']['booklogr'] }}"
- name: "BOOKLOGR_OIDC_CLIENT_SECRET"
value: "{{ hostvars['console']['booklogr']['oidc']['secret'] }}"
notify: "notification_restart_booklogr"
no_log: true
- name: Pull image
containers.podman.podman_image:
name: "{{ item }}"
tag: "{{ version['containers']['booklogr'] }}"
state: "present"
pull: true
loop:
- "docker.io/mozzo/booklogr"
- "docker.io/mozzo/booklogr-web"
- name: Deploy booklogr.container file
ansible.builtin.template:
src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/booklogr/{{ item }}.j2"
dest: "{{ node['home_path'] }}/.config/containers/systemd/{{ item }}"
owner: "{{ ansible_user }}"
group: "{{ node['group'] }}"
mode: "0644"
loop:
- "booklogr-api.container"
- "booklogr-web.container"
notify: "notification_restart_booklogr"
- name: Enable booklogr.service
ansible.builtin.systemd:
name: "{{ item }}"
state: "started"
enabled: true
daemon_reload: true
scope: "user"
loop:
- "booklogr-api.service"
- "booklogr-web.service"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/{{ item.file }}.tar"
loop:
- name: "booklogr"
image: "docker.io/mozzo/booklogr:{{ version['containers']['booklogr'] }}"
file: "docker.io_mozzo_booklogr_{{ version['containers']['booklogr'] }}"
- name: "booklogr-web"
image: "docker.io/mozzo/booklogr-web:{{ version['containers']['booklogr'] }}"
file: "docker.io_mozzo_booklogr-web_{{ version['containers']['booklogr'] }}"
loop_control:
label: "{{ item.name }}"
register: container_archive_images
- name: Save container archive images
containers.podman.podman_save:
image:
- "{{ item.item.image }}"
dest: "{{ node['home_path'] }}/archives/containers/{{ item.item.file }}.tar"
format: "oci-archive"
force: false
loop: "{{ container_archive_images.results }}"
loop_control:
label: "{{ item.item.file }}"
when: not item.stat.exists
- name: Fetch container archive images
ansible.builtin.fetch:
src: "{{ node['home_path'] }}/archives/containers/{{ item.item.file }}.tar"
dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/"
flat: true
loop: "{{ container_archive_images.results }}"
loop_control:
label: "{{ item.item.file }}"
@@ -157,7 +157,7 @@
- name: Deploy nextcloud-cron service
ansible.builtin.copy:
src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/nextcloud/systemd/{{ item }}"
src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/nextcloud/services/{{ item }}"
dest: "{{ node['home_path'] }}/.config/systemd/user/{{ item }}"
owner: "{{ ansible_user }}"
group: "{{ node['group'] }}"
@@ -0,0 +1,111 @@
---
- name: Create papra directory
ansible.builtin.file:
path: "{{ node['home_path'] }}/{{ item }}"
state: "directory"
owner: "{{ services['papra']['subuid'] }}"
group: "{{ node['group'] }}"
mode: "0770"
loop:
- "data/containers/papra"
- "data/containers/papra/data"
- "data/containers/papra/backup"
become: true
- name: Pull image
containers.podman.podman_image:
name: "ghcr.io/papra-hq/papra"
tag: "{{ version['containers']['papra'] }}"
state: "present"
pull: true
- name: Register secret value to podman secret
containers.podman.podman_secret:
name: "{{ item.name }}"
data: "{{ item.value }}"
state: "present"
force: true
loop:
- name: "PAPRA_AUTH_SECRET"
value: "{{ hostvars['console']['papra']['secret_key'] }}"
- name: "PAPRA_AUTH_PROVIDERS_CUSTOMS"
value: |
[
{
"providerId": "authelia",
"providerName": "Authelia",
"providerIconUrl": "https://www.authelia.com/images/branding/logo-cropped.png",
"clientId": "papra",
"clientSecret": "{{ hostvars['console']['papra']['oidc']['secret'] }}",
"type": "oidc",
"discoveryUrl": "https://{{ services['authelia']['domain']['public'] }}.{{ domain['public'] }}/.well-known/openid-configuration",
"scopes": ["openid", "profile", "email"]
}
]
notify: "notification_restart_papra"
no_log: true
- name: Deploy papra.container file
ansible.builtin.template:
src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/papra/papra.container.j2"
dest: "{{ node['home_path'] }}/.config/containers/systemd/papra.container"
owner: "{{ ansible_user }}"
group: "{{ node['group'] }}"
mode: "0644"
become: true
notify: "notification_restart_papra"
- name: Deploy papra-backup service
ansible.builtin.copy:
src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/papra/services/{{ item }}"
dest: "{{ node['home_path'] }}/.config/systemd/user/{{ item }}"
owner: "{{ ansible_user }}"
group: "{{ node['group'] }}"
mode: "0644"
loop:
- "papra-backup.service"
- "papra-backup.timer"
- name: Deploy papra exporter
ansible.builtin.copy:
src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/papra/script/papra-exporter.sh"
dest: "{{ node['home_path'] }}/data/containers/papra/"
owner: "{{ ansible_user }}"
group: "{{ node['group'] }}"
mode: "0755"
- name: Enable papra.service
ansible.builtin.systemd:
name: "papra.service"
state: "started"
enabled: true
daemon_reload: true
scope: "user"
- name: Enable papra-backup.timer
ansible.builtin.systemd:
name: "papra-backup.timer"
state: "started"
enabled: true
daemon_reload: true
scope: "user"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/ghcr.io_papra-hq_papra_{{ version['containers']['papra'] }}.tar"
register: container_archive_images
- name: Save container archive images
containers.podman.podman_save:
image:
- "ghcr.io/papra-hq/papra:{{ version['containers']['papra'] }}"
dest: "{{ node['home_path'] }}/archives/containers/ghcr.io_papra-hq_papra_{{ version['containers']['papra'] }}.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/ghcr.io_papra-hq_papra_{{ version['containers']['papra'] }}.tar"
dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/"
flat: true
@@ -0,0 +1,146 @@
---
- name: Set redis service name
ansible.builtin.set_fact:
redis_service: "romm"
- name: Create redis_romm directory
ansible.builtin.file:
path: "{{ node['home_path'] }}/{{ item }}"
state: "directory"
owner: "{{ services['redis']['subuid'] }}"
group: "{{ node['group'] }}"
mode: "0770"
loop:
- "containers/redis"
- "containers/redis/{{ redis_service }}"
- "containers/redis/{{ redis_service }}/data"
become: true
- name: Deploy redis config file
ansible.builtin.template:
src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/redis/redis.conf.j2"
dest: "{{ node['home_path'] }}/containers/redis/{{ redis_service }}/redis.conf"
owner: "{{ ansible_user }}"
group: "{{ node['group'] }}"
mode: "0644"
register: "is_redis_conf"
- name: Deploy redis container file
ansible.builtin.template:
src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/redis/redis.container.j2"
dest: "{{ node['home_path'] }}/.config/containers/systemd/redis_{{ redis_service }}.container"
owner: "{{ ansible_user }}"
group: "{{ node['group'] }}"
mode: "0644"
register: "is_redis_containerfile"
- name: Enable (Restart) redis service
ansible.builtin.systemd:
name: "redis_{{ redis_service }}.service"
state: "restarted"
enabled: true
daemon_reload: true
scope: "user"
when: is_redis_conf.changed or is_redis_containerfile.changed # noqa: no-handler
- name: Create romm directory
ansible.builtin.file:
path: "{{ node['home_path'] }}/{{ item }}"
state: "directory"
owner: "{{ services['romm']['subuid'] }}"
group: "{{ node['group'] }}"
mode: "0770"
loop:
- "data/containers/romm"
- "data/containers/romm/library"
- "data/containers/romm/resources"
- "data/containers/romm/assets"
- "containers/romm"
- "containers/romm/ssl"
become: true
- name: Deploy root certificate
ansible.builtin.copy:
content: |
{{ hostvars['console']['ca']['root']['crt'] }}
dest: "{{ node['home_path'] }}/containers/romm/ssl/{{ root_cert_filename }}"
owner: "{{ ansible_user }}"
group: "{{ node['group'] }}"
mode: "0440"
notify: "notification_restart_romm"
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: "ROMM_AUTH_SECRET_KEY"
value: "{{ hostvars['console']['romm']['secret_key'] }}"
- name: "ROMM_DATABASE_PASSWORD"
value: "{{ hostvars['console']['postgresql']['password']['romm'] }}"
- name: "ROMM_OIDC_CLIENT_SECRET"
value: "{{ hostvars['console']['romm']['oidc']['secret'] }}"
notify: "notification_restart_romm"
no_log: true
- name: Pull image
containers.podman.podman_image:
name: "docker.io/rommapp/romm"
tag: "{{ version['containers']['romm'] }}"
state: "present"
pull: true
- name: Deploy romm.container file
ansible.builtin.template:
src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/romm/romm.container.j2"
dest: "{{ node['home_path'] }}/.config/containers/systemd/romm.container"
owner: "{{ ansible_user }}"
group: "{{ node['group'] }}"
mode: "0644"
notify: "notification_restart_romm"
- name: Enable romm.service
ansible.builtin.systemd:
name: "romm.service"
state: "started"
enabled: true
daemon_reload: true
scope: "user"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/{{ item.file }}.tar"
loop:
- name: "redis"
image: "docker.io/library/redis:{{ version['containers']['redis'] }}"
file: "docker.io_library_redis_{{ version['containers']['redis'] }}"
- name: "romm"
image: "docker.io/rommapp/romm:{{ version['containers']['romm'] }}"
file: "docker.io_rommapp_romm_{{ version['containers']['romm'] }}"
loop_control:
label: "{{ item.name }}"
register: container_archive_images
- name: Save container archive images
containers.podman.podman_save:
image:
- "{{ item.item.image }}"
dest: "{{ node['home_path'] }}/archives/containers/{{ item.item.file }}.tar"
format: "oci-archive"
force: false
loop: "{{ container_archive_images.results }}"
loop_control:
label: "{{ item.item.file }}"
when: not item.stat.exists
- name: Fetch container archive images
ansible.builtin.fetch:
src: "{{ node['home_path'] }}/archives/containers/{{ item.item.file }}.tar"
dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/"
flat: true
loop: "{{ container_archive_images.results }}"
loop_control:
label: "{{ item.item.file }}"
@@ -106,7 +106,7 @@
set +a
/usr/bin/kopia repository create filesystem \
--path="{{ node['home_path'] }}/data/kopia/repository" \
--description="ilnmors.internal kopia repository" \
--description="{{ domain['internal'] }} kopia repository" \
--create-only
when: not is_kopia_repository.stat.exists
changed_when: true
@@ -138,17 +138,16 @@
--user-password {{ item.password | quote }} \
{{ item.name }}
loop:
- name: "console@console.ilnmors.internal"
- name: "console@console.{{ domain['internal'] }}"
password: "{{ hostvars['console']['kopia']['users']['console'] }}"
- name: "infra@infra.ilnmors.internal"
- name: "infra@infra.{{ domain['internal'] }}"
password: "{{ hostvars['console']['kopia']['users']['infra'] }}"
- name: "app@app.ilnmors.internal"
- name: "app@app.{{ domain['internal'] }}"
password: "{{ hostvars['console']['kopia']['users']['app'] }}"
when:
- not is_kopia_repository.stat.exists
- is_repository_connected.rc == 0
changed_when: true
register: "is_user_created"
no_log: true
- name: Set kopia console ACL
@@ -157,7 +156,7 @@
. /etc/secrets/{{ node['uid'] }}/kopia.env
set +a
/usr/bin/kopia server acl add \
--user console@console.ilnmors.internal \
--user console@console.{{ domain['internal'] }} \
--target {{ item }} \
--access FULL
loop:
@@ -178,7 +177,7 @@
. /etc/secrets/{{ node['uid'] }}/kopia.env
set +a
/usr/bin/kopia server acl add \
--user infra@infra.ilnmors.internal \
--user infra@infra.{{ domain['internal'] }} \
--target {{ item.target }} \
--access {{ item.access }}
loop:
@@ -186,7 +185,7 @@
access: "FULL"
- target: "type=policy"
access: "READ"
- target: "type=policy,username=infra,hostname=infra.ilnmors.internal"
- target: "type=policy,username=infra,hostname=infra.{{ domain['internal'] }}"
access: "FULL"
- target: "type=content"
access: "APPEND"
@@ -202,7 +201,7 @@
. /etc/secrets/{{ node['uid'] }}/kopia.env
set +a
/usr/bin/kopia server acl add \
--user app@app.ilnmors.internal \
--user app@app.{{ domain['internal'] }} \
--target {{ item.target }} \
--access {{ item.access }}
loop:
@@ -210,7 +209,7 @@
access: "FULL"
- target: "type=policy"
access: "READ"
- target: "type=policy,username=app,hostname=app.ilnmors.internal"
- target: "type=policy,username=app,hostname=app.{{ domain['internal'] }}"
access: "FULL"
- target: "type=content"
access: "APPEND"
@@ -232,7 +231,7 @@
--global \
--compression=zstd
- content: |
infra@infra.ilnmors.internal \
infra@infra.{{ domain['internal'] }} \
--compression=zstd \
--keep-latest=10 \
--keep-hourly=0 \
@@ -241,7 +240,7 @@
--keep-monthly=6 \
--keep-annual=1
- content: |
app@app.ilnmors.internal \
app@app.{{ domain['internal'] }} \
--compression=none \
--keep-latest=10 \
--keep-hourly=0 \
+3 -2
View File
@@ -1,3 +1,4 @@
---
- name: Restart ca
ansible.builtin.systemd:
name: "ca.service"
@@ -73,7 +74,7 @@
listen: "notification_restart_grafana"
ignore_errors: true # noqa: ignore-errors
- name: Restart x509-exporter.service
- name: Restart x509-exporter
ansible.builtin.systemd:
name: "x509-exporter.service"
state: "restarted"
@@ -84,7 +85,7 @@
listen: "notification_restart_x509-exporter"
ignore_errors: true # noqa: ignore-errors
- name: Restart maddy.service
- name: Restart maddy
ansible.builtin.systemd:
name: "maddy.service"
state: "restarted"
@@ -59,12 +59,11 @@
# The reason why this task doesn't use the way to check ".init" file is this tasks can override original database.
# Absent of ".init" file cannot guarantee DB is empty.
when: false
become: true
no_log: true
block:
- name: Register extra secret value to podman secret
containers.podman.podman_secret:
name: "LLDAP_LDAP_USER_PASSWORD"
name: "LLDAP_LDAP_USER_PASS"
data: "{{ hostvars['console']['ldap']['password']['services']['admin'] }}"
state: "present"
force: true
@@ -83,15 +82,17 @@
- "LLDAP_DATABASE_URL,type=env"
- "LLDAP_KEY_SEED,type=env"
- "LLDAP_JWT_SECRET,type=env"
- "LLDAP_LDAP_USER_PASSWORD,type=env"
- "LLDAP_LDAP_USER_PASS,type=env"
volumes:
- "{{ node['home_path'] }}/containers/ldap/data:/data:rw"
- "{{ node['home_path'] }}/containers/ldap/ssl:/etc/ssl/ldap:ro"
etc_hosts:
"{{ {} | combine({ services['postgresql']['domain']['internal'] ~ '.' ~ domain['internal']: 'host-gateway' }) }}"
always:
- name: Clean extra secret value from podman secret
containers.podman.podman_secret:
name: "LLDAP_LDAP_USER_PASSWORD"
name: "LLDAP_LDAP_USER_PASS"
state: "absent"
- name: Pull image
+37 -21
View File
@@ -132,7 +132,6 @@ postgresql:
vaultwarden: ENC[AES256_GCM,data:BPj5eFo54DTZ82n3yTIqEbm7kb/jWT0n2kZY//oV5q48eRch3C2RBuxn/Ko=,iv:DGC4ipHMyVs25gc4sNMt8LN1RsHjiR/b303vgiFoxMY=,tag:k1eb4DoRPLKvvMstSI1faQ==,type:str]
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]
nextcloud: ENC[AES256_GCM,data:ROsximNuWYMTZktmLJPx7W1Qol/uT+APgwoCtFO/6ZYYc3KxKvlk344eqEc=,iv:4d+MrfIHjJKAcwhvZ3g4go66uZcieuL7lngKErJd+fg=,tag:QbWOtxeCbiu62GyrE2atXg==,type:str]
sure: ENC[AES256_GCM,data:FULJ2gjJ2gZC3s324itW+CjGRBHIP9RnOqw5TT1UaiUhb7UHAPm1na+LsZk=,iv:c0GnVZkxprJUzPPq3TCQaZvAes9QQuvDXqgVLLaiQIg=,tag:uDxy/Lkd2hNK4AWwMNMslw==,type:str]
outline: ENC[AES256_GCM,data:0eJFCfHgS4olWXHOnUPixs4Dl5PoxJa+2H1mk3YgC4GUv+nX8gTPmzKTMVM=,iv:HGxX7aLODUc2nVLC8YXdet9qphU/wxMapWhk93YmHDM=,tag:6/2uckW/a24LgVFRH6E85Q==,type:str]
@@ -140,6 +139,8 @@ postgresql:
mealie: ENC[AES256_GCM,data:XbYHQv6HBWCXUBRmcbQZN5Y1Gw+V+Wra28Mp+m+ZGl8VRXsKMu5507vGHQM=,iv:zp4ZarqG9KshiBV8F8UksfI2QXmXHwqPw631r/5bYy8=,tag:jvsvXrUsCvpQGIbLV9jTYQ==,type:str]
homebox: ENC[AES256_GCM,data:EhNnltOBwLai5vEMt7Dih5Ug6h3ojrdbwhq4Vr0tg/p/0nqI9L7O/Le0B4w=,iv:14oyl916t0inr+W/lztQCjYiSQ0vIIwC9NmbezDuvvY=,tag:IiXZd3NU+dUvDrHpxJXFew==,type:str]
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]
#ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment]
#
#
@@ -147,8 +148,8 @@ postgresql:
ldap:
key: ENC[AES256_GCM,data:PwQ/AtsQbbM454iM0wpUjGRfxyxNY5lGDARv/XOdk0yMCHRsf9qHKaW37EwcqNe+MVTJH+sx8lHDdxRYF1gO1yj6kasEIbMeo9cIaGQHvey5dYdber/qUkcjuAXyhSZGSUXI3PR1CuAsiqNUEruh/eKyDZAvHTTY0Rc5jk4HLFzPqqBjBOMkA6VQXJ2I+v4s8W0T8/JEQ5l6avwYbaaInIIY+uPV+sY0mBczAuaHLyeA3V7VQNTxQ9lUgdfH/TpgBn9EBH2wWqIGk2zQ6XmVrmOarCo/+JqwLQVEyY5TTrNuBto=,iv:iYzoRXjH8NjPnX5nBhU2gTQXOekXMirjTGhmpGt3YAs=,tag:IxwtGNXx1i54Jnhn1aZx3w==,type:str]
crt: ENC[AES256_GCM,data:ulOZOUV23qfU2RATrXYFKPnPNW2vHShJe3O90XXoSA4bkUBA5Jz8uuAzCiVOZfx/Ni2uG6y5pP2x4ZnJADSrkfHD5P5IF3iUWWSgRo4NDDCY00r+fuPFP5DpOhwHl4LzdiHl8SBJ6YWBbhtT8sXVXFgmQIFLppuUD7WzJ4U2c251vF6G7Q0XemlkkNFq4fbkClTwogUyTRs2Dj0e187uilDkfCBS83oxMU5pB5tBvaFbLxaJmtNpaFRp0U2fgJqJrM7avZJeNUDr1hHttAw/TnANNK3X3mgFAruluTUGTS0dt8KPUlaUCf00I0t1F6l+cNktaTlnUtcp/HdWf0Rtg+tYBfFNgsGl3iB2z1utpab+6WsI2+LZYGezOLKojbFeYGNmVLkSucCSVAtuj48fqKFeObdLcBjihXilI151ie4m5BR+37h1Laob64C/n8lhNSm8QpTuIipV/3koyBKwIimMYYO8j8sHGQNJpdDuZXmYCEiXsdXZH2tFngWtlJ+39yV+GCjhKRYPkAWFKw+EtTSnPposC8fHPMLMZnB1hpsVmSy6VBDuZ2Qq96H+njgzWXIwhWcZteTyxp2fS6dNSG3zTYD2cp9qVrA4KhTlAjVojXdlRAv5Ayob9R89hD3WNKexe3QD/Nj1Hva7GyjEHqHijodYDNXCWsMgXdgItgwUz28vh+H8o5sSHCsfUDM2s7Y4w1rmKOkD4XpxTcyGM5zrNBacvhS1FzuDFYZ8BnN+STaQ1pvscVGtrys6EtXDBfrWbrIFnIhMGSp/GGUMhrT30gPV5VmzUqixRJzpKvYF1ljr8ybRu9Sf4xaprQNeFTTWq2KXonwK8JonbZJNwQQ6oDbp7AyuQD4EJD8GoXJUAy9H5TjnpjJsZH+RsTR6O19K+O45HQCqvZpEHfUUXu022VqrjmzRHnmB4bJKgb+BqE6YpQ==,iv:Vh9eqEun0E73s57RV53rHbjP5AWXn4NPjlEPwVDQ5VY=,tag:fxpzhMHk8Jmb60GHOHnYvw==,type:str]
seed_key: ENC[AES256_GCM,data:q6kodsHzwDuf0UHPSk9VbEI9pCwePJWcA7o6dQiZqyM=,iv:EbozH0UpLThEXjmWkNhkpHQxabH/K3/lJ8xki3lLGZo=,tag:rKO8csevxpH5kzzFWQoang==,type:str]
jwt_secret: ENC[AES256_GCM,data:AfPcqKAwdsHfiYHrZ6yKqOxD93gBcLEvZ4N3M8nhWMQ=,iv:awfSXZbc7Tzg8sRqUkp71TXivLb663ZjaxNkskE1Heg=,tag:e541/ejk7x+GOhJeoUPMGA==,type:str]
seed_key: ENC[AES256_GCM,data:1hv0p1uhHoJQduFLu/mJ3QvnFJUsjkeclnW4TXAszT411ceN4i5V8NIXImI=,iv:vf/lDc+A7wXX4MlXvfKSYzUUYuai4j8qlGQhA5CR85M=,tag:o8MQzkImlDLOPvO+7o440Q==,type:str]
jwt_secret: ENC[AES256_GCM,data:m3wA70WxYPGWxrcsHpFcbK+M4EkntA2ySWWS/chV/CoSa9fx4jHqgW4kiuA=,iv:VYCga9S19s7SsXt1KoTR2El7aOtADymNNUR57g6+sHA=,tag:eUUF1nPV8STOWGI3WaVB/g==,type:str]
#ENC[AES256_GCM,data:/5Po+8hjJg74ZBepaNWTzZy7JrTjiwo2DNFT76hZTUFKEdchNd4sfdy3tbVwL5y9QPC/kC3kvBZL2oA0+WzcUmd51Ht4/xS6CnUCpodBrSWNHu4H+ohdmz/jMplREeJVWAFHuTZROqB9v3hw3rYJ5Gb+5XROAuzT4pKfrPoJ1B+ysGUpuvybhrC1O5Tg6IPs5fvIiR3yExGcIFuywZBiiA==,iv:gupWDnXb2TD1N8GhHwpiYuz0sDciLX8V0ww9cFKtFNI=,tag:7eDx5RZOrNXOUshvdijV5w==,type:comment]
#ENC[AES256_GCM,data:+Q0n+z/P2NwlY6/aOzMNe2yZaoN7uSctcH8DlTUR6dYgNTnZlGQNNFAHs416XVMcYHyBqPwkSvdQLGl5TcJReZLm+8ESeYeQhjWgHZWKnGJK0e46cdMhznXKjQ==,iv:dayGt24JAY3+LV3OS0JUxM7vxfOUAoTl7O33d6y4wVI=,tag:KcfHxeqjmyAUfNDELBfx/w==,type:comment]
#ENC[AES256_GCM,data:6AE31A6F6mflPwwac4jVxY5kJPYaKlYzNjtUC9/VmCPeS8iuvoFmxHNQ9cwxLa20Nw==,iv:N7tQ9RyBYvSCKzbLGWgH8NgyKCJpNM5cupGK9e9pQbM=,tag:nAduK50DDywtkkQ03psKRA==,type:comment]
@@ -196,9 +197,9 @@ grafana:
#
#ENC[AES256_GCM,data:k+Y8/BDL9G+C1EnnJjy7gz4=,iv:IU/ZlXVvtX+zaeU7Zcz5baYk8ceDz95dZgzpl5/L9lg=,tag:t0OzLQCM+Up0wLkc3B/R/Q==,type:comment]
authelia:
jwt_secret: ENC[AES256_GCM,data:NOsbV8poM41bodRZeNJ6vnRYIl7vGKXm2BpBNeXWVeE=,iv:1G4PnaoopJMIt7D+8x0x3ui5tefMPRnSI5KJRv4IuTo=,tag:EkEHgeJ3ta4htrgOCnYldg==,type:str]
session_secret: ENC[AES256_GCM,data:tPudIXLrAKMhR3VPFj8U1g/+FRvYgBo0wXLNf/a72x0=,iv:atfRSZNUYEYFK92+0WOvsD5Gi4tMEKv+dwsFcRDrEtM=,tag:cJu5G4EYQ9EnYOQ4w1OSpA==,type:str]
storage_secret: ENC[AES256_GCM,data:g3Z8t/2xFZJMz4baIfY7pGoAOdoKsr+Xn4POBl1lQCk=,iv:i9Owc4w76qC1rFSUXLv4NuW6/Y2rmgSoTr/EnZuRVE0=,tag:zitZ/UAgXiUSuyWDhvHa9A==,type:str]
jwt_secret: ENC[AES256_GCM,data:YwMsvOqmukD/UyLZZOxHyN7VCy9yT6ZIkWHCtRdb3Qh12/5QFMUrsuy9HC0=,iv:yg3dTjGAKpBCLT2ixfF99mqnKj+OMogs3uU748ouLwk=,tag:AeGAfyIfGY2FxCvJfHUd0w==,type:str]
session_secret: ENC[AES256_GCM,data:PPO8MhdI9EgfXFJsu7/FsCa+cudZ+34bq2lP709NDFdzLK4Svdy9JkhZqsE=,iv:Zfq/NDpbPtEgt7fmv9Z0kIL/nQUGALAJ5bEVaMPD8tA=,tag:FG2Nii5QeJY8rREKAtmdZQ==,type:str]
storage_secret: ENC[AES256_GCM,data:uORmJjre1o1JjjEzuI3qdLPW8Vgld1bwp5lrzrfLMhce35Iw9wm1UM0WkW8=,iv:ajDOHOo6AXI3r+32nd8nj+PK/pLb9BXkExIYkN90C4g=,tag:OOdraFk6SudP1GEn0Eox9Q==,type:str]
hmac_secret: ENC[AES256_GCM,data:c6Xn0QceaQWIFDCFr6UAQcr9M/Np2aGCkcNiXJfM79vFakT5L8q27+38Mvs=,iv:QSpnN/ZVYNogHfFq10E1rR8jXnI/qLs3Z7k2qKHFt4A=,tag:N6Wx52yCmGx2kJlyQXl7bQ==,type:str]
jwk_rs256: ENC[AES256_GCM,data:e0rbiDCUadbuRL3dFZ//LMMYimQ3THJtZo1OqkKntwayh10DbvdNrEhs6HF0VAMtVh5nkAaL69V3tqZnwLUadSg0Oomum8v9GkTE527iupAMjWRrXzAR/nsjvjI8gPUL3NJk++v0VA6ew/Gb9WfGlhUlmkf3K72Ab+gGiD8+xfAFeqRE/k7vHkTxkNwD1hRFnE+XhkUa4Q021nSd7xrW80sHwBWTKKUqA08xuouQN8Gkbjj22NgicT5rhy4VJiV8OnLCERLWc8F6LmDM9vLrTiPBvsGn1eX09fvVRtw40Wo0VSgklm9+REQtjefBhXuVxVDR3DXNzPliLNL3MOrvlcweDU8uxFVuml23FBBkJypJLJwbWvxwFQk1OWjiQ9jPel9+9gYu1xHg/dKeEsDf8GyMVJz60BDEQH5YnEwI1R6rPnJLZnmZVhRtoxmKRE8/oE3lP30tvOVKwRL/f3rXCyB1PYDR/2NsWtj61inzgn1BuMDl/tHDKjGhsCu2+ECThD756p6cjQnGftSSoGlwhmMCqA8I+6WUjMPqEsqietWKbMCRk000ufLp57wU4i7BvUVPSpiVO58lf6n/zs6hHBYDWNfoxEa2gha4csmCTkhCrzf+N+UJQmoMDOIQe/gSqPNcwcW/+q8K8zr2b4cMIyLbwZIyiez0RvPRnM/KuM9UmFAKfW1xdtyxalNLMOBjpqeZFnCIPFRQy6COP/dZNA9xUQX4TXXsL+qTk0vlU41/gcORgw9HQFRkLiRtrnqvRUMXipIDU6SIRipbBJ1GawH5nPTdHdbjdr7Wh0s2RUjIzLNvT0Lz0VRii2dlVRdURbtFl8R8frijuE9LG9xKY2Z4f/ojo71h6HcJvrvMOGm4iE8V5+cdthlwn4Hpyl8nLinJWLyVjhc4bzmIlo+VZvbq9o/keSfCb3wrI39L3Wy/bpoKzXx/U3eTVIIXRDrBpK+4fmYeSEi2JRJjAHHvs8De7c0wKLq9qOctIFvELwueisDIkyNGYykX1+JXa4YbuTYaOSLJNbk9B6UE17Vbr6FD9N/8n5JveXzfd2dTROQBbWmHyKa0qR7MC9foPdyUVImj1Z06w91OwRyAi+/GBiUE8ivKD9AmnTjrvX1uZhp9qGSzP9f3RkCd6lFDQ0YhSCVHQAYOrDsdBX50zqqmZEPPevIXXPnaJpSgIaOCamBLIHqrxX9KOFM3ApicE2BhXX4xN2QWFl8HGu2cVD1e9bINKrpLGqR+rQQE822OQqMqw3kZsi7lo46YPOrpXLsetxPJlLftQ7bdaCPONZk3M18xvxESYclDeGk7LJ8Lp5CjtBterF3Ka/0r7cY4OxWusTypOY3jmVDILZTMlVOKoYiJgjIHxwXFdoqbtGMeGi83CY2/SfoHn7BSrU2669O3liX0L7hzGOLerkiVozbmC3TPq+JxGNw7dA79mXCqRDQEeqAjeha6J3wmvTvdmkuPIbO7ryFtDg9VwbULfgl07WzkES5nVyQDeamKv76ERRF/EIjQGgiWRY5MDyXAhVnzy9ATEbOnBTXeks6863CTKa/3lhhtDUGeo+9tTXcEUqLPkq1jh9xYo80OUp99J1483n5iE7EEBfB13McRwsJPufViyMWIQX9KDmRZmbCjhe/Jrj6joapQcY1Fw5MaZJgfIIfzz83GnekgTG9sz7Vfq2p4qiPDONLhY6qL9ENl8Cm/tWq/Xjvk+v/gaT65L9uvGLtkYKv7OpbPtTzieGHtYmddLZNr34Ly06PHxajLL+X6LeB5wmeC7jvanLh9vW6VqwNRn6kstS9reM4kHRhYTNKlDB9pOOz/cK0p0wcNzVvqetoYtZAX9TgPcYumhZQUPsXYsuC2Gdo88seihGKND9jtepvu2OwKRT3raSpsG+rum86Q95ulRsE/rhg4BvBXs8U62E16gL1KamtRkPcjXT/2aO1t0gh6JiBbQAYemnOaJCanycbnQcZal5oSvqPLAxzFpcjTkPNVFhYyiGe0ekcaYkCs9PYuM3hqP+qNAwjQmK7bfpK+eZNGT1gwikmlUtokTvRvQjmFynfbQ5oUoOHQuxuqWOa8KLi2NwMMmaJ4LXuzozUY8KD2EXmlmrxteCZBtQNfa7BxulUMlPRHG5bqKsBc9l380I1XU+aR2qmqHxdrd9698sIXxVK5zM1vhwI0fb6V61u86iYIAdSoitzhRQu8lK8prJXxcZWOQWehV03wvDnqkU4F2Qm/XYCXJgTx8PJyGKP2w90K7TtkW1YMWrBXmnwr,iv:av1JOLQd7WoZr4O8e9PU8nEZlaf+Bs7LLbGZs/4hm88=,tag:sZ8zyjRmhyYRdwntXTOfPg==,type:str]
jwk_rs256_public: ENC[AES256_GCM,data:2IP+w7X5ZfYUH+upr7qtjno45RXXUT71hG9Xs/VMY9aWOv6KGLpK17OEo7hVUaXwmOWOzAQl0V79bGZjIowabAoba15+IEi38L973i6F44kM9pb4N29Z2LjmLoZ/blNpl82C0ZIO/+OQ+OCRxVV8b9eZ+E1ckpvEk24NmOV2o9H2E1EU6QBOQGmYt39mnD/tQKNtE0/Q0+HbW2bz9dWqTScjH6CcwBwhtn3dhU/kbXjLc27XG6pxMKInKLo6GZmjLA3PDRQY9O2cYnFN1nDRKdOqHpbEnAPU4DiUSronJZGq6R6Z+3JGkWXH2OUTXaP8itLpXYBioY8BKH02QA+YVxnzxC0cBooNW9BxFb/GQdeLqFawNZ2lH9+743svXIRjjKp3jyn1Bn0TnDxgirZSfmvBdrgOy1fKl0qpMBYrBVLaAP1+T4MxNNa1kQqnmt9wke7aYrjVroi3/33TE9oU/xW6SW6Y+3lLEB6kJIGYb/m+qhwksOrqXRDom8G5hocUqMX3Q1fkiv8Jt/qvmuz8oSFNsjVF/4AZFESacfAF9pyerPUemyti6Y+rVjeY1yUv3+BHGgu8OiF7lqlIUm5O7NGhNQ==,iv:41P+xnnSOQK4H1dm5EohB5FmXsuaKXfSOFr+ROgoTWw=,tag:7a0+DsaDDK1DlkggQQd6vQ==,type:str]
@@ -248,18 +249,6 @@ immich:
#ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment]
#
#
#ENC[AES256_GCM,data:McPUAbIUvtC1gdPaxTgAxAMCMWcLfg==,iv:Tp6idRf7he3sYzo8LW596C905JAaoTIhIoDUzSyRT0k=,tag:4mZQ0Swu1X9uuwjsRNhr2A==,type:comment]
paperless:
session_secret: ENC[AES256_GCM,data:siwCs2noeVpg9DCEZybnmo/oz11BdrHSTnHciMOu/6g=,iv:XVjhu10TIujIdUopN9+TVVqRade9EvItDWxym6YXnZs=,tag:TxLYm+4Bo7IMaTQBtMg9pQ==,type:str]
admin:
id: ENC[AES256_GCM,data:8uo=,iv:f/qtpDb1Xe0M6Gd2VFP2vXULCd7WOn7TCxLlaK1dDbA=,tag:eNRMPJtLlA3Vjj79bSFxuA==,type:str]
password: ENC[AES256_GCM,data:TAA+TRTYRUSw47nE7oPDQmOtCBZpf2WyxUIIzftWq16wQhseODhH0R74X+Q=,iv:V0tzSvW5iYzEUgcxempmyD9ermVAqg673liiysT4jJ4=,tag:GuxtZ7EvgwvlFWwg+C6Jsg==,type:str]
oidc:
secret: ENC[AES256_GCM,data:wjRDVCJsINM4z5946a6uZD+6bhN5BChLMdRzgMEJFGRGFNcXd7A1p2Iqn4I=,iv:Y4QDA09L8ULKr4hhvoiduzCD8Hifo1gAnpzjCr8e520=,tag:R0RvGxYnXo3zwykXJykRug==,type:str]
hash: ENC[AES256_GCM,data:pali6WwPNhJA+6QL4O+tKv42PnpGqmojb8JQUZLqxGizv1bJSCgdUN8upCy5Ke0DYZs5P+JY5vh23xfMZFnHduGxGwOuPX6J5lYgvJRV58LqS3/+yIBBprTJyro3MwsurTTEWesgKMr8/2H9lirhaLjWUOSPxAmQ6e4wPNpHycDVyj4=,iv:cg2trI7t1MfIcMo1/M+IY6JEl2msDoKRGgAx/Y5nyGk=,tag:gnOq7sBq9z5zrRY0yhIabg==,type:str]
#ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment]
#
#
#ENC[AES256_GCM,data:PZS7EbvMHqHGorNUGAWj4dk1,iv:vOE+djRAvBTMM51kHi6kG5Arw3uPXlJt1d/BpcEaD0c=,tag:AuoCHLQz42CYvVVdKFWu1Q==,type:comment]
nextcloud:
admin:
@@ -273,7 +262,7 @@ nextcloud:
#
#ENC[AES256_GCM,data:Fsqc2JDp9dvfgiCjdQ==,iv:3DALKKEXaP8hzXRvxD4CgfFpOiPPsOa16OB94n8WKp8=,tag:K+FF3zGrc0YLXWK/R2L3Ow==,type:comment]
sure:
secret_key: ENC[AES256_GCM,data:jZLkIoLG7Y5JaMlfG7XTVnrgmM3JiJUfYEXzi4XHukE=,iv:VkgZbr9GDMQuAA4xvls9eNp479ylus0LHzLqArKt1Bk=,tag:7+g7JOEQUzZaKc1JYeCwmA==,type:str]
secret_key: ENC[AES256_GCM,data:95YY5PQXvNkHLhzpgO+av+J+f+ttN/adfRr5kspNXy5CNl44sh7F9OcHmXM=,iv:c25O1a4yrPaPQ9CTCS74UWGAAiijkAASKoaPQBOBstY=,tag:LHQilc4NfLjLTVytGM+pUQ==,type:str]
oidc:
secret: ENC[AES256_GCM,data:9BSvpcU9BJctSN9bkPIAsRxg8JNHTWvOKdpJFhm//CUDm/Xc7oC/ANHf5no=,iv:JVQLl/rp65kZSK/4SpVXxtiac3Z35XNkxWm2+lEdq/c=,tag:WgfaORiNlrO+wHSdnl4CWQ==,type:str]
hash: ENC[AES256_GCM,data:EjJ+1fP7/9wG2jG0Jv2hxMLtErqxjHBstRjru79dd5ZXhqwT7S+jpLfl9WpZU9qi20ps9YP4qe7G08p6NJNXjYhQj852GQxEORRh/9StAZsPt3p8w+ePZSVbivPQH+FpPKWYxoH0VR7y3TnL66R0tKRLh1fNTc5jRy5rU5r1bfs1jZ0=,iv:0y9FxW4QdD7qHz3bPRWlwHFpvOsvlYhVrOItB6BzaE8=,tag:Wc7MZhP3QRYmvZcjpoEWtQ==,type:str]
@@ -332,6 +321,33 @@ mastodon:
#ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment]
#
#
#ENC[AES256_GCM,data:I6O+AZ4EGMl6dXBowhMBG+c=,iv:bQ5mvEfVHDyTwNI+5hCsUkWEowP8fqir6lqLYPmsnbY=,tag:ta69wItzleRbky3vjU4+WA==,type:comment]
booklogr:
secret_key: ENC[AES256_GCM,data:yImb0UKqDIeyCmThb3upJPIxZVF7wiaLboP8PO/K2yjCjI0qXOgSkXJbrlw=,iv:yQjqT4831bqbRYgjQ72Vcg6omweWWPTgj6ZUHeaSGWA=,tag:y0BQxLTQklFP5hmJjBNAMw==,type:str]
oidc:
secret: ENC[AES256_GCM,data:iLFtafeePxJuC2NyV9hPzkxqgR+67k23Szgl1SPZtuOE1wS6a9YVroTQKMo=,iv:1w0Jy0N4pu5O1h+DYtzAUv0Qd75pRNxV5l38fyrDIWI=,tag:mZ11TOrJzC1sxBxjcOEagQ==,type:str]
hash: ENC[AES256_GCM,data:0ShwGvr69/J+mNZ8+ALL4hEbouABbWXcj1kMV9M8jbyhe48FwikaeKY1SKTQRDB3XFMdRhOR+NFRDQhLvNN/7RW16ZPF2jkj/am4MEUQ0z8jd+imK98cpb7XuXb6DuU1cqtuKhG0lJcFQaHOCNXqSWoPi8oRUwHIpvGjXa3Wr4hC/pw=,iv:iPXaqonARTWV19E0RUIPt05DJ9iDKSS8skcT4xf8wYc=,tag:n6S6x5Qeh+M3itRopj9SbQ==,type:str]
#ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment]
#
#
#ENC[AES256_GCM,data:BmZppA02YtiTykTtUnk=,iv:zV2x6W1yWhMegE+h7zdQNRxVrN7C16Lq4xpDkkZPcBw=,tag:MPGH1vjS1IYuZo2qf30qwA==,type:comment]
papra:
secret_key: ENC[AES256_GCM,data:AoqqUS2OUb11UwWNteZHqU6FF1eJVbmvANfZRvcwnvU+/cbYtPC8cHyRcBQ=,iv:6eWM/9NDApVMpTviYw2X1p91VItG3qlkGocjrkLyBq8=,tag:STXAVUqQEHGW+ijRQOZLrA==,type:str]
oidc:
secret: ENC[AES256_GCM,data:a019OZyi6HSP4t4IcDVOATKnrwIHooq9nh3g5mzq4x9fpcbM5o6UW3kCNlE=,iv:7ibp5/fCq/qGM3RcVbpl94OjGjuXL+Y1qvsx9t693aw=,tag:Sv2K89OBPGQFkag0vXenxw==,type:str]
hash: ENC[AES256_GCM,data:bHVmhaBo49OHH7ZGRdZvRcOyRZrorTFk+63H5esDwUAqZfXJfTVML9naOtoeYElPnh+Zx0EX06ozHwuqgK9ZcGVAi3hoxnWQeImKfVvlifazGIcVK8omNguPd8n5PhooqS6AiFixiizvTM6iLp1TvKLTCb8QywOVq7RxykJPu3XXDAA=,iv:/EsPo3bMtVYoGtyHydW96o404ZRI2Tqop76XozEZlhw=,tag:HlWGa096oG61Ua7Fr1OjPA==,type:str]
#ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment]
#
#
#ENC[AES256_GCM,data:5KE0gQZnd8NY49wg9A==,iv:L5SdkwVrYxujpgHMHgL6U8fNP7v+qMHBpfRyDAa8/Ds=,tag:mWwHMiR48Shu5DOssdMuNA==,type:comment]
romm:
secret_key: ENC[AES256_GCM,data:i+Dltol9oS9r+kIFoPUJ8sJsIkgDyW3W9C9SVWetZoFhC3b2UWwBpyJO+ZM=,iv:TDkP+D0jWkh/d6wgsOcyOfyCOvGud8L8hIEKtpvMThY=,tag:n/Hl0KEbauZVpixrfrTakg==,type:str]
oidc:
secret: ENC[AES256_GCM,data:G0knyOyj3oNuUCIQF0cb+/UkiZlk7/7FxABuqdbybXgJoC2xYkbGgesedaw=,iv:Wd3+phjBsdYSYV4+y+KPIFlX6sWVFGvJCdkyiIa0tII=,tag:1joAPQUXiKfvhyhj+DM+Uw==,type:str]
hash: ENC[AES256_GCM,data:Z36CsPTYBLC/qM3uiSkSjll2CL3+HwHxyu3k/JWpg/cquz1kkYE6jHzdSSPYR28uQiAxB28CVRV0zhgAFjhsloIV3MTzkTKRkTwHibJmkFUxRjkmynhUTOFqkKH88s+WMM3bZvIwrauvYnVtSknxg6lwmmUOaAdXZh/bdbW3bVChHXE=,iv:5p55hVvFrdWFjbjVjooLWhFSqSVInTAsfQlgyOvF4ls=,tag:YW6SSxEvQOOkLbaKNmGPvQ==,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:
@@ -348,7 +364,7 @@ sops:
k+uyWPPi+enFkVaceSwMFrA66uaWWrwAj11sXEB7yzvGFPrnAGezjQ==
-----END AGE ENCRYPTED FILE-----
recipient: age120wuwcmsm845ztsvsz46pswj5je53uc2n35vadklrfqudu6cxuusxetk7y
lastmodified: "2026-07-05T12:35:46Z"
mac: ENC[AES256_GCM,data:fTpqEj1RulJgsW1ueIvSaZIabHGjG/bCk7KutUW+mYXjL2OUskFejnWIbw/vCVaqv/eQsDHRQWL7SSt5SVFOUwv9v6vqXdj/ZIEMIcF3hE3T65vt2Rr9kuB5kjaOwWNzKDUY32Gb1v4MmP2aEG4D7Lj0dVVrej85T6Nizpg0uc8=,iv:8+jqnFxmLSTi247Tu1HXzXUPWqnty0QVn1mSSLk4z9E=,tag:vct9Gq7kMwo+TwghBWKhkA==,type:str]
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]
unencrypted_suffix: _unencrypted
version: 3.13.2
@@ -0,0 +1,51 @@
[Quadlet]
DefaultDependencies=false
[Unit]
Description=BookLogr API
After=network-online.target
Wants=network-online.target
[Container]
Image=docker.io/mozzo/booklogr:{{ version['containers']['booklogr'] }}
ContainerName=booklogr-api
HostName=booklogr-api
PublishPort={{ services['booklogr']['ports']['api'] }}:5000/tcp
Volume=%h/data/containers/booklogr/profile_pictures:/app/profile_pictures:rw
Volume=%h/data/containers/booklogr/export_data:/app/export_data:rw
Volume=%h/containers/booklogr/ssl:/etc/ssl/booklogr:ro
# General
Environment="TZ={{ timezone }}"
Environment="SINGLE_USER_MODE=false"
Environment="EXPORT_FOLDER=/app/export_data"
# PostgreSQL
Environment="DATABASE_URL=postgresql://booklogr@{{ services['postgresql']['domain']['internal'] }}.{{ domain['internal'] }}:{{ services['postgresql']['ports']['tcp'] }}/booklogr_db"
Environment="PGSSLMODE=verify-full"
Environment="PGSSLROOTCERT=/etc/ssl/booklogr/{{ root_cert_filename }}"
Secret=BOOKLOGR_POSTGRES_PASSWORD,type=env,target=PGPASSWORD
# Authentication
Environment="AUTH_ALLOW_REGISTRATION=true"
Environment="AUTH_REQUIRE_VERIFICATION=false"
Secret=BOOKLOGR_AUTH_SECRET_KEY,type=env,target=AUTH_SECRET_KEY
# OIDC - Authelia
Environment="OIDC_CLIENT_ID=booklogr"
Environment="OIDC_REDIRECT_URI=https://{{ services['booklogr']['domain']['public'] }}.{{ domain['public'] }}/callback"
Environment="OIDC_DISCOVERY_URL=https://{{ services['authelia']['domain']['public'] }}.{{ domain['public'] }}/.well-known/openid-configuration"
Secret=BOOKLOGR_OIDC_CLIENT_SECRET,type=env,target=OIDC_CLIENT_SECRET
[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
@@ -0,0 +1,42 @@
[Quadlet]
DefaultDependencies=false
[Unit]
Description=BookLogr Web
After=network-online.target booklogr-api.service
Wants=network-online.target booklogr-api.service
[Container]
Image=docker.io/mozzo/booklogr-web:{{ version['containers']['booklogr'] }}
ContainerName=booklogr-web
HostName=booklogr-web
PublishPort={{ services['booklogr']['ports']['http'] }}:80/tcp
# General
Environment="TZ={{ timezone }}"
Environment="BL_DEMO_MODE=false"
Environment="BL_DEBUG=false"
Environment="BL_CHECK_UPDATES=true"
# API
Environment="BL_API_ENDPOINT=https://{{ services['booklogr']['domain']['public'] }}.{{ domain['public'] }}"
# Authentication UI
Environment="BL_SINGLE_USER_MODE=false"
Environment="BL_ALLOW_MANUAL_LOGIN=false"
Environment="BL_ALLOW_REGISTRATION=false"
# OIDC - Authelia
Environment="BL_OIDC_AUTHORITY=https://{{ services['authelia']['domain']['public'] }}.{{ domain['public'] }}"
Environment="BL_OIDC_CLIENT_ID=booklogr"
[Service]
Restart=always
RestartSec=10s
TimeoutStopSec=120
[Install]
WantedBy=default.target
@@ -7,6 +7,9 @@
href: https://chatgpt.com
- School:
- Job Schedule:
- abbr: LIB
href: https://docs.google.com/spreadsheets/d/1yaTmzANAKL5edrunH_G5k8cpNRHoLoAAyn5AplI_Y0I/htmlview#gid=0
- Portal:
- abbr: SJU
href: https://portal.sejong.ac.kr/
@@ -12,7 +12,7 @@ Image=ghcr.io/mastodon/mastodon-streaming:{{ version['containers']['mastodon'] }
ContainerName=mastodon-streaming
HostName=mastodon-streaming
PublishPort={{ services['mastodon']['ports']['streaming'] }}:4000/tcp
PublishPort={{ services['mastodon']['ports']['api'] }}:4000/tcp
Volume=%h/containers/mastodon/ssl:/etc/ssl/mastodon:ro
@@ -0,0 +1,43 @@
[Quadlet]
DefaultDependencies=false
[Unit]
Description=Papra
[Container]
Image=ghcr.io/papra-hq/papra:{{ version['containers']['papra'] }}
ContainerName=papra
HostName=papra
PublishPort={{ services['papra']['ports']['http'] }}:1221/tcp
# Volumes
Volume=%h/data/containers/papra/data:/app/app-data:rw
Volume=%h/data/containers/papra/backup:/app/backup:rw
# General
Environment="TZ={{ timezone }}"
Environment="APP_BASE_URL=https://{{ services['papra']['domain']['public'] }}.{{ domain['public'] }}"
# Authentication
Environment="AUTH_PROVIDERS_EMAIL_IS_ENABLED=false"
Environment="AUTH_IS_PASSWORD_RESET_ENABLED=false"
Environment="AUTH_SHOW_LEGAL_LINKS=false"
Secret=PAPRA_AUTH_SECRET,type=env,target=AUTH_SECRET
# OIDC
Secret=PAPRA_AUTH_PROVIDERS_CUSTOMS,type=env,target=AUTH_PROVIDERS_CUSTOMS
# Documents
Environment="DOCUMENTS_CONTENT_EXTRACTION_ENABLED=true"
Environment="DOCUMENTS_OCR_LANGUAGES=eng,kor"
Environment="DOCUMENT_STORAGE_MAX_UPLOAD_SIZE=0"
[Service]
Restart=always
RestartSec=10s
TimeoutStopSec=120
[Install]
WantedBy=default.target
@@ -0,0 +1,151 @@
#!/bin/bash
set -Eeuo pipefail
URL=""
ORG_ID=""
TOKEN=""
output_dir="$PWD/papra-export"
page_size=100
page_index=0
downloaded=0
# usage() function
usage() {
echo "Usage: $0 -u \"URL\" -o \"ORG_ID\" -t \"TOKEN\""
echo "-u <URL>: Specify the URL of Papra, including https://"
echo "-o <ORG_ID>: Specify the organization ID of Papra"
echo "-t <TOKEN>: Specify the token to access the Papra API"
echo "Token requires READ permissions"
exit 1
}
# Log function
log() {
local timestamp
local level="$1"
local msg="$2"
timestamp="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
echo "time=\"$timestamp\" level=\"$level\" msg=\"$msg\" source=\"papra-exporter.sh\"" >&2
}
# getopts to get arguments
while getopts ":u:o:t:" opt; do
case "$opt" in
u)
URL="$OPTARG"
;;
o)
ORG_ID="$OPTARG"
;;
t)
TOKEN="$OPTARG"
;;
\?) # unknown option
log "error" "Invalid option: -$OPTARG"
usage
;;
:) # parameter required
log "error" "Option -$OPTARG requires an argument"
usage
;;
esac
done
# Validate required arguments
if [[ -z "$URL" || -z "$ORG_ID" || -z "$TOKEN" ]]; then
log "error" "URL, ORG_ID and TOKEN are required"
usage
fi
# Require an explicit HTTPS URL
if [[ "$URL" != https://* ]]; then
log "error" "URL must start with https://"
exit 1
fi
# Remove trailing slash
URL="${URL%/}"
# Always create a fresh export
rm -rf -- "$output_dir"
mkdir -p -- "$output_dir"
while true; do
response="$(
curl \
--fail \
--silent \
--show-error \
--get \
--header "Authorization: Bearer $TOKEN" \
--data-urlencode "pageIndex=$page_index" \
--data-urlencode "pageSize=$page_size" \
"$URL/api/organizations/$ORG_ID/documents"
)"
count="$(jq -r '.documents | length' <<<"$response")"
total="$(jq -r '.documentsCount' <<<"$response")"
while IFS= read -r document; do
id="$(jq -r '.id' <<<"$document")"
name="$(jq -r '.name // empty' <<<"$document")"
# Prevent document names from creating subdirectories.
name="${name//\//_}"
if [[ -z "$name" || "$name" == "." || "$name" == ".." ]]; then
name="$id"
fi
destination="$output_dir/$name"
# Preserve both files when names collide.
if [[ -e "$destination" ]]; then
if [[ "$name" == *.* && "$name" != .* ]]; then
stem="${name%.*}"
extension=".${name##*.}"
destination="$output_dir/${stem}__${id}${extension}"
else
destination="$output_dir/${name}__${id}"
fi
fi
temporary="${destination}.part"
rm -f -- "$temporary"
if ! curl \
--fail \
--silent \
--show-error \
--location \
--header "Authorization: Bearer $TOKEN" \
--output "$temporary" \
"$URL/api/organizations/$ORG_ID/documents/$id/file"
then
rm -f -- "$temporary"
log "error" "Failed to download document: $id"
exit 1
fi
mv -f -- "$temporary" "$destination"
downloaded=$((downloaded + 1))
printf '[%d/%d] %s\n' \
"$downloaded" \
"$total" \
"${destination#"$output_dir"/}"
done < <(jq -c '.documents[]' <<<"$response")
if (( count == 0 || downloaded >= total )); then
break
fi
page_index=$((page_index + 1))
done
log "info" "Export completed: $downloaded document(s)"
@@ -0,0 +1,68 @@
[Unit]
Description=Papra sqlite backup
Requires=papra.service
After=papra.service
[Service]
Type=oneshot
# Create a consistent SQLite snapshot and atomically publish it.
ExecStart=/usr/bin/podman exec -u nonroot papra node --input-type=module -e '\
import { createClient } from "@libsql/client"; \
import { rename, rm } from "node:fs/promises"; \
\
/* Paths */ \
const databaseUrl = "file:/app/app-data/db/db.sqlite"; \
const backupDir = "/app/backup"; \
\
/* Build the backup name from the container-local date. */ \
const now = new Date(); \
const date = [ \
now.getFullYear(), \
String(now.getMonth() + 1).padStart(2, "0"), \
String(now.getDate()).padStart(2, "0"), \
].join("-"); \
\
const dst = backupDir + "/db_" + date + ".sqlite"; \
const tmp = dst + ".tmp"; \
\
/* Never expose a partially-created backup as a completed one. */ \
let completed = false; \
try { \
await rm(tmp, { force: true }); \
\
/* Create a transactionally consistent SQLite snapshot. */ \
const db = createClient({ url: databaseUrl }); \
try { \
await db.execute({ \
sql: "VACUUM INTO ?", \
args: [tmp], \
}); \
} finally { \
db.close(); \
} \
\
/* Publish only after VACUUM INTO has completed successfully. */ \
await rename(tmp, dst); \
completed = true; \
} finally { \
if (!completed) { \
await rm(tmp, { force: true }); \
} \
}'
ExecStart=/usr/bin/podman exec -u nonroot papra sh -c "find /app/backup/ -maxdepth 1 -type f -name 'db_*.sqlite' -mtime +7 -delete"
# Verify
#podman exec -u nonroot -w /app papra \
# node --input-type=module -e '
# const m=await import(process.argv[1]);
# const db=m.createClient({url:process.argv[2]});
# try {
# const r=await db.execute("PRAGMA quick_check");
# console.log(r.rows);
# } finally {
# db.close();
# }
# ' \
# "@libsql/client" \
# "file:/app/backup/test-backup.sqlite"
# > Result: [ { quick_check: 'ok' } ]
@@ -0,0 +1,14 @@
[Unit]
Description=Run Papra Data Backup service every day
[Timer]
# Execute service after 1 min on booting
OnBootSec=1min
OnCalendar=*-*-* 23:50:00
# When timer is activated, Service also starts.
Persistent=true
[Install]
WantedBy=timers.target
@@ -0,0 +1,80 @@
[Quadlet]
DefaultDependencies=false
[Unit]
Description=RomM
After=redis_romm.service
Wants=redis_romm.service
[Container]
Image=docker.io/rommapp/romm:{{ version['containers']['romm'] }}
ContainerName=romm
HostName=romm
PublishPort={{ services['romm']['ports']['http'] }}:8080/tcp
# Volumes
Volume=%h/data/containers/romm/library:/romm/library:rw
Volume=%h/data/containers/romm/resources:/romm/resources:rw
Volume=%h/data/containers/romm/assets:/romm/assets:rw
Volume=%h/containers/romm/ssl:/etc/ssl/romm:ro
# General
Environment="TZ={{ timezone }}"
Environment="ROMM_BASE_URL=https://{{ services['romm']['domain']['public'] }}.{{ domain['public'] }}"
Environment="DISABLE_SETUP_WIZARD=true"
Environment="DISABLE_USERPASS_LOGIN=true"
# Secrets
Secret=ROMM_AUTH_SECRET_KEY,type=env,target=ROMM_AUTH_SECRET_KEY
# Database
Environment="ROMM_DB_DRIVER=postgresql"
Environment="DB_HOST={{ services['postgresql']['domain']['internal'] }}.{{ domain['internal'] }}"
Environment="DB_PORT={{ services['postgresql']['ports']['tcp'] }}"
Environment="DB_NAME=romm_db"
Environment="DB_USER=romm"
Environment="PGSSLMODE=verify-full"
Environment="PGSSLROOTCERT=/etc/ssl/romm/{{ root_cert_filename }}"
Secret=ROMM_DATABASE_PASSWORD,type=env,target=DB_PASSWD
# Redis
Environment="REDIS_HOST=host.containers.internal"
Environment="REDIS_PORT={{ services['romm']['ports']['redis'] }}"
# OIDC
Environment="OIDC_ENABLED=true"
Environment="OIDC_PROVIDER=authelia"
Environment="OIDC_CLIENT_ID=romm"
Environment="OIDC_REDIRECT_URI=https://{{ services['romm']['domain']['public'] }}.{{ domain['public'] }}/api/oauth/openid"
Environment="OIDC_SERVER_APPLICATION_URL=https://{{ services['authelia']['domain']['public'] }}.{{ domain['public'] }}"
Environment="OIDC_USERNAME_ATTRIBUTE=preferred_username"
Environment="OIDC_ALLOW_REGISTRATION=true"
Environment="OIDC_CLAIM_ROLES=groups"
Environment="OIDC_ROLE_ADMIN=admins"
Environment="OIDC_ROLE_VIEWER=users"
Environment="OIDC_AUTOLOGIN=false"
Secret=ROMM_OIDC_CLIENT_SECRET,type=env,target=OIDC_CLIENT_SECRET
# Players
Environment="DISABLE_EMULATOR_JS=false"
Environment="DISABLE_RUFFLE_RS=false"
# Metadata
Environment="HASHEOUS_API_ENABLED=true"
#Environment="PLAYMATCH_API_ENABLED=true"
# Optional
Environment="LOGLEVEL=INFO"
Environment="DISABLE_LOGS_VIEWER=true"
[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
@@ -55,7 +55,7 @@ access_control:
# authelia portal
- domain: '{{ services['authelia']['domain']['public'] }}.{{ domain['public'] }}'
policy: 'bypass'
# - domain: 'test.ilnmors.com'
# - domain: 'test.example.com'
# policy: 'one_factor'
# subject:
# - 'group:admins'
@@ -118,6 +118,23 @@ identity_providers:
access_token: '1h'
id_token: '1h'
refresh_token: '720h'
cors:
# booklogr cors configuration
endpoints:
- 'authorization'
- 'token'
- 'userinfo'
allowed_origins:
- 'https://{{ services['booklogr']['domain']['public'] }}.{{ domain['public'] }}'
claims_policies:
romm:
id_token:
- 'email'
- 'email_verified'
- 'alt_emails'
- 'preferred_username'
- 'name'
- 'groups'
clients:
# https://www.authelia.com/integration/openid-connect/clients/gitea/
- client_id: 'gitea'
@@ -165,29 +182,6 @@ 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/paperless/
- client_id: 'paperless'
client_name: 'Paperless'
consent_mode: 'implicit'
client_secret: '{{ hostvars['console']['paperless']['oidc']['hash'] }}'
public: false
authorization_policy: 'one_factor'
require_pkce: true
pkce_challenge_method: 'S256'
redirect_uris:
- 'https://{{ services['paperless']['domain']['public'] }}.{{ domain['public'] }}/accounts/oidc/authelia/login/callback/'
scopes:
- 'openid'
- 'profile'
- 'email'
- 'groups'
response_types:
- 'code'
grant_types:
- 'authorization_code'
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/nextcloud/#openid-connect-user-backend-app
- client_id: 'nextcloud'
client_name: 'Nextcloud'
@@ -345,3 +339,71 @@ identity_providers:
grant_types:
- 'authorization_code'
token_endpoint_auth_method: 'client_secret_basic'
# https://booklogr.app/docs/Configuration/OIDC-sign-in
- client_id: 'booklogr'
client_name: 'booklogr'
consent_mode: 'implicit'
client_secret: '{{ hostvars['console']['booklogr']['oidc']['hash'] }}'
public: false
authorization_policy: 'one_factor'
require_pkce: false
pkce_challenge_method: ''
redirect_uris:
- 'https://{{ services['booklogr']['domain']['public'] }}.{{ domain['public'] }}/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_post'
# https://www.authelia.com/integration/openid-connect/clients/papra/
- client_id: 'papra'
client_name: 'papra'
consent_mode: 'implicit'
client_secret: '{{ hostvars['console']['papra']['oidc']['hash'] }}'
public: false
authorization_policy: 'one_factor'
require_pkce: false
pkce_challenge_method: ''
redirect_uris:
- 'https://{{ services['papra']['domain']['public'] }}.{{ domain['public'] }}/api/auth/oauth2/callback/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_post'
# https://www.authelia.com/integration/openid-connect/clients/romm/
- client_id: 'romm'
claims_policy: 'romm'
client_name: 'romm'
consent_mode: 'implicit'
client_secret: '{{ hostvars['console']['romm']['oidc']['hash'] }}'
public: false
authorization_policy: 'one_factor'
require_pkce: false
pkce_challenge_method: ''
redirect_uris:
- 'https://{{ services['romm']['domain']['public'] }}.{{ domain['public'] }}/api/oauth/openid'
scopes:
- 'openid'
- 'groups'
- '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'
@@ -23,7 +23,6 @@
{% if node['name'] == 'app' %}
{
servers {
# Only accept packets from auth main caddy
trusted_proxies static {{ hostvars['fw']['network']['auth']['ipv4']['server'] }} {{ hostvars['fw']['network']['auth']['ipv6']['server'] }}
}
}
@@ -84,9 +83,13 @@
{% elif node['name'] == 'app' %}
{{ service['domain']['internal'] }}.{{ domain['internal'] }} {
import private_tls
{% if service['ports']['streaming'] is defined %}
@streaming path /api/v1/streaming*
reverse_proxy @streaming host.containers.internal:{{ service['ports']['streaming'] }} {
{% if service['route'] is defined and service['route']['deny'] is defined %}
@deny path {{ service['route']['deny'] }}
respond @deny 404
{% endif %}
{% if service['route'] is defined and service['route']['api'] is defined %}
@api path {{ service['route']['api'] }}
reverse_proxy @api host.containers.internal:{{ service['ports']['api'] }} {
header_up Host {http.request.header.X-Forwarded-Host}
}
{% endif %}
@@ -2,7 +2,11 @@
Description=Daily Kopia backup timer
[Timer]
OnCalendar=*-*-* 03:00:00
{% if node['name'] == "app" %}
OnCalendar=*-*-* 00:00:00
{% elif node['name'] == "infra" %}
OnCalendar=*-*-* 01:00:00
{% endif %}
Persistent=true
RandomizedDelaySec=300
@@ -37,8 +37,7 @@ conditional:
{{ network['reverse_zone']['ipv4']['server'] }}: "{{ hostvars['fw']['network']['bind']['ipv4']['server'] }}, {{ hostvars['fw']['network']['bind']['ipv6']['server'] }}"
{{ network['reverse_zone']['ipv6']['client'] }}: "{{ hostvars['fw']['network']['bind']['ipv4']['server'] }}, {{ hostvars['fw']['network']['bind']['ipv6']['server'] }}"
{{ network['reverse_zone']['ipv6']['server'] }}: "{{ hostvars['fw']['network']['bind']['ipv4']['server'] }}, {{ hostvars['fw']['network']['bind']['ipv6']['server'] }}"
vpn.{{ domain['public'] }}: "tcp-tls:1.1.1.1:853, tcp-tls:1.0.0.1:853, tcp-tls:[2606:4700:4700::1111]:853, tcp-tls:[2606:4700:4700::1001]:853"
{{ services['wireguard']['domain']['public'] }}.{{ domain['public'] }}: "tcp-tls:1.1.1.1:853, tcp-tls:1.0.0.1:853, tcp-tls:[2606:4700:4700::1111]:853, tcp-tls:[2606:4700:4700::1001]:853"
blocking:
blockType: nxDomain
denylists:
@@ -6,7 +6,7 @@
- Create the password with `openssl rand -base64 32`
- Save this value in secrets.yaml in `postgresql.password.ezbookkeeping`
- Access infra server to create paperless_db with `podman exec -it postgresql psql -U postgres`
- Access infra server to create ezbookkeeping_db with `podman exec -it postgresql psql -U postgres`
```SQL
CREATE USER ezbookkeeping WITH PASSWORD 'postgresql.password.ezbookkeeping';
@@ -0,0 +1,23 @@
# https://www.authelia.com/integration/openid-connect/clients/paperless/
- client_id: 'paperless'
client_name: 'Paperless'
consent_mode: 'implicit'
client_secret: ''
public: false
authorization_policy: 'one_factor'
require_pkce: true
pkce_challenge_method: 'S256'
redirect_uris:
- 'https://paperless.example.com/accounts/oidc/authelia/login/callback/'
scopes:
- 'openid'
- 'profile'
- 'email'
- 'groups'
response_types:
- 'code'
grant_types:
- 'authorization_code'
access_token_signed_response_alg: 'none'
userinfo_signed_response_alg: 'none'
token_endpoint_auth_method: 'client_secret_post'
@@ -0,0 +1,14 @@
services:
paperless:
domain:
public: ""
internal: ""
ports:
http: ""
redis: ""
subuid: "100999"
dependency:
web: ""
db: ""
version:
paperless: "2.20.15"
@@ -0,0 +1,13 @@
---
postgresql:
password:
paperless: ""
paperless:
session_secret: ""
admin:
id: ""
password: ""
oidc:
secret: ""
hash: ""
@@ -0,0 +1,21 @@
---
identity_providers:
oidc:
# readur
- client_id: 'readur'
client_name: 'readur'
consent_mode: 'implicit'
client_secret: ''
public: false
authorization_policy: 'one_factor'
redirect_uris:
- 'https://readur.example.com/api/auth/oidc/callback'
scopes:
- 'openid'
- 'profile'
- 'email'
response_types:
- 'code'
grant_types:
- 'authorization_code'
token_endpoint_auth_method: 'client_secret_basic'
@@ -0,0 +1,13 @@
services:
readur:
domain:
public: ""
internal: ""
ports:
http: ""
redis: ""
dependency:
web: ""
db: ""
version:
readur: "2.9.1"
@@ -0,0 +1,47 @@
[Quadlet]
DefaultDependencies=false
[Unit]
Description=Readur
[Container]
Image=ghcr.io/readur/readur:{{ version['containers']['readur'] }}
ContainerName=readur
HostName=readur
PublishPort={{ services['readur']['ports']['http'] }}:8000/tcp
# Volumes
Volume=%h/data/containers/readur/uploads:/app/uploads:rw
Volume=%h/data/containers/readur/watch:/app/watch:rw
Volume=%h/containers/readur/ssl:/etc/ssl/readur:ro
# General
Environment="TZ={{ timezone }}"
Environment="ALLOW_LOCAL_AUTH=false"
Environment="OCR_LANGUAGE=eng+kor"
Secret=READUR_JWT_SECRET,type=env,target=JWT_SECRET
# Database
Secret=READUR_DATABASE_URL,type=env,target=DATABASE_URL
# OIDC
Environment="OIDC_ENABLED=true"
Environment="OIDC_CLIENT_ID=readur"
Environment="OIDC_ISSUER_URL=https://{{ services['authelia']['domain']['public'] }}.{{ domain['public'] }}"
Environment="OIDC_REDIRECT_URI=https://{{ services['readur']['domain']['public'] }}.{{ domain['public'] }}/api/auth/oidc/callback"
Environment="OIDC_AUTO_REGISTER=true"
Secret=READUR_OIDC_CLIENT_SECRET,type=env,target=OIDC_CLIENT_SECRET
# Logging
Environment="RUST_LOG=warn"
[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
@@ -0,0 +1,10 @@
---
postgresql:
password:
readur: ""
readur:
jwt_secret: ""
oidc:
secret: ""
hash: ""
@@ -0,0 +1,148 @@
---
- name: Set redis service name
ansible.builtin.set_fact:
redis_service: "readur"
- name: Create redis_readur directory
ansible.builtin.file:
path: "{{ node['home_path'] }}/{{ item }}"
state: "directory"
owner: "{{ services['redis']['subuid'] }}"
group: "{{ node['group'] }}"
mode: "0770"
loop:
- "containers/redis"
- "containers/redis/{{ redis_service }}"
- "containers/redis/{{ redis_service }}/data"
become: true
- name: Deploy redis config file
ansible.builtin.template:
src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/redis/redis.conf.j2"
dest: "{{ node['home_path'] }}/containers/redis/{{ redis_service }}/redis.conf"
owner: "{{ ansible_user }}"
group: "{{ node['group'] }}"
mode: "0644"
register: "is_redis_conf"
- name: Deploy redis container file
ansible.builtin.template:
src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/redis/redis.container.j2"
dest: "{{ node['home_path'] }}/.config/containers/systemd/redis_{{ redis_service }}.container"
owner: "{{ ansible_user }}"
group: "{{ node['group'] }}"
mode: "0644"
register: "is_redis_containerfile"
- name: Enable (Restart) redis service
ansible.builtin.systemd:
name: "redis_{{ redis_service }}.service"
state: "restarted"
enabled: true
daemon_reload: true
scope: "user"
when: is_redis_conf.changed or is_redis_containerfile.changed # noqa: no-handler
- name: Create readur directory
ansible.builtin.file:
path: "{{ node['home_path'] }}/{{ item }}"
state: "directory"
owner: "{{ ansible_user }}"
group: "{{ node['group'] }}"
mode: "0770"
loop:
- "data/containers/readur"
- "data/containers/readur/uploads"
- "data/containers/readur/watch"
- "containers/readur"
- "containers/readur/ssl"
become: true
- name: Deploy root certificate
ansible.builtin.copy:
content: |
{{ hostvars['console']['ca']['root']['crt'] }}
dest: "{{ node['home_path'] }}/containers/readur/ssl/{{ root_cert_filename }}"
owner: "{{ ansible_user }}"
group: "{{ node['group'] }}"
mode: "0440"
become: true
notify: "notification_restart_readur"
no_log: true
- name: Pull image
containers.podman.podman_image:
name: "ghcr.io/readur/readur"
tag: "{{ version['containers']['readur'] }}"
state: "present"
pull: true
- name: Register secret value to podman secret
containers.podman.podman_secret:
name: "{{ item.name }}"
data: "{{ item.value }}"
state: "present"
force: true
loop:
- name: "READUR_JWT_SECRET"
value: "{{ hostvars['console']['readur']['jwt_secret'] }}"
- name: "READUR_DATABASE_URL"
value: "postgresql://readur:{{ hostvars['console']['postgresql']['password']['readur'] | urlencode | replace('/', '%2F') }}\
@{{ services['postgresql']['domain']['internal'] }}.{{ domain['internal'] }}/readur_db?sslmode=verify-full&\
sslrootcert=/etc/ssl/readur/{{ root_cert_filename }}"
- name: "READUR_OIDC_CLIENT_SECRET"
value: "{{ hostvars['console']['readur']['oidc']['secret'] }}"
notify: "notification_restart_readur"
no_log: true
- name: Deploy readur.container file
ansible.builtin.template:
src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/readur/readur.container.j2"
dest: "{{ node['home_path'] }}/.config/containers/systemd/readur.container"
owner: "{{ ansible_user }}"
group: "{{ node['group'] }}"
mode: "0644"
notify: "notification_restart_readur"
- name: Enable readur.service
ansible.builtin.systemd:
name: "readur.service"
state: "started"
enabled: true
daemon_reload: true
scope: "user"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/{{ item.file }}.tar"
loop:
- name: "redis"
image: "docker.io/library/redis:{{ version['containers']['redis'] }}"
file: "docker.io_library_redis_{{ version['containers']['redis'] }}"
- name: "readur"
image: "ghcr.io/readur/readur:{{ version['containers']['readur'] }}"
file: "ghcr.io_readur_readur_{{ version['containers']['readur'] }}"
loop_control:
label: "{{ item.name }}"
register: container_archive_images
- name: Save container archive images
containers.podman.podman_save:
image:
- "{{ item.item.image }}"
dest: "{{ node['home_path'] }}/archives/containers/{{ item.item.file }}.tar"
format: "oci-archive"
force: false
loop: "{{ container_archive_images.results }}"
loop_control:
label: "{{ item.item.file }}"
when: not item.stat.exists
- name: Fetch container archive images
ansible.builtin.fetch:
src: "{{ node['home_path'] }}/archives/containers/{{ item.item.file }}.tar"
dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/"
flat: true
loop: "{{ container_archive_images.results }}"
loop_control:
label: "{{ item.item.file }}"