Compare commits

..

34 Commits

Author SHA1 Message Date
il f28661e664 refactor(convention): add existing 'site' tag to convention 2026-05-15 10:16:36 +09:00
il a09712c142 refactor(script): update edit_secret.sh to optimize log print logic 2026-05-15 10:12:42 +09:00
il a7e2320b21 chore(script): archive a extract_secret.sh script
archived stack: extract_secret.sh
2026-05-15 09:19:59 +09:00
il 24c83029e9 refactor(playbook): update convention and remove deprecated tag
update notes:
- remove tags 'update', 'upgrade' from convention.yaml
- remove tags 'update' from playbooks/app/site.yaml
2026-05-15 09:04:51 +09:00
il ac64b3c04e docs(readme): add RPO on readme 2026-05-13 17:12:59 +09:00
il 26d696f813 refactor(all): update hardcoded internal domain to ansible variable 2026-05-12 08:08:04 +09:00
il 1096981ef2 feat(paperless): change paperless OCR engine model from tesseract_fast to tesseract_best 2026-05-12 08:00:37 +09:00
il e1936b494d fix(crowdsec): update whitelist.yaml to prevent false positive
false positive:
- nextcloud chunk problem (crowdsecurity/http-crawl-non_statics)
- change expression 'chunks.mjs' to 'chunk.mjs'
2026-05-11 19:40:50 +09:00
il 0afc841b69 chore(chromium): archive a removed stack from console
archived stack: chromium
2026-05-11 19:37:25 +09:00
il a39122eb4b fix(crowdsec): update whitelist.yaml to prevent false positive
false positive:
- nextcloud chunk problem (crowdsecurity/http-crawl-non_statics)
2026-05-11 19:34:22 +09:00
il 0f4da0bb53 feat(backup): add archiving of runtime binary packages 2026-05-11 01:37:15 +09:00
il 1dd1c53e2a feat(backup): add archiving of deployed container images 2026-05-11 00:52:28 +09:00
il 530407c162 refactor(all): update hardcoded timezone 'Asia/Seoul' to ansible variable 'timezone' 2026-05-10 18:44:28 +09:00
il 11ab2f5205 fix(sure): correct task name and subuid variable reference 2026-05-10 14:39:54 +09:00
il 4527e39d0f chore(app): archive removed stacks from app
archived stacks:
- actual-budget
- ezbookkeeping
- opencloud
- trilium
- vikunja
- wikijs
2026-05-10 00:07:51 +09:00
il 02fa912cb1 feat(trilium): release trilium
deployment notes:
- oidc error (users cannot access at once, it needs login twice when using oidc
2026-05-09 22:38:57 +09:00
il aceef4bdaa refactor(authelia): update authelia.yaml.j2 to fix redirect_uris from hardcoded uris to ansible variables 2026-05-09 21:44:11 +09:00
il 64aad4fcf0 docs(all): fix markdown syntax and snippets 2026-05-09 20:54:32 +09:00
il 81244d55a7 feat(wiki.js): release wiki.js
deployment notes:
- use this as personal/family wiki system
- compare to affine / memos and triliumNext
2026-05-09 17:50:05 +09:00
il 1cfd024285 refactor(x509-exporter): update notification to restart x509-exporter when its config.yaml is changed 2026-05-09 17:42:35 +09:00
il 26115c5660 feat(redis): update redis from 8.6.1 to 8.6.3
update notes:
- run 'ansible-playbook playbooks/app/site.yaml --tags "site"' so that update all redis at onece
2026-05-09 13:55:28 +09:00
il acef35ca8b feat(postgresql): update postgresql and vectorchord extension
update notes:
- update postgresql version from 18.2 to 18.3
- update vectorchord version from 0.5.3 to 1.1.1
- add update flow and notice to postgresql.md
2026-05-09 13:54:10 +09:00
il b531170bd7 feat(vaultwardne): update vaultwarden from 1.35.8 to 1.36.0 2026-05-09 12:56:22 +09:00
il ad586c3cd3 feat(grafana): update grafana from 12.3.3 to 13.0.1 2026-05-09 12:50:36 +09:00
il 6dfef08f7b feat(prometheus): update prometheus from v3.9.1 to v3.11.3 2026-05-09 12:44:44 +09:00
il 934dd314a8 feat(x509-exporter): update x509-exporter from 3.21.0 to 4.1.0
update notes:
- '--listen-address' and '--watch-dir' cli flags are deprecated
- add '--config' cli flag and config.yaml
2026-05-09 12:44:05 +09:00
il 2529a918df feat(loki): update loki version from 3.6.5 to 3.7.1 2026-05-09 12:17:16 +09:00
il 7dfa20d3dd feat(ldap): update lldap version from 0.6.2 to 0.6.3 2026-05-09 11:56:19 +09:00
il 329620c7d7 feat(alloy): update alloy version from 1.13.0 to 1.16.1 2026-05-09 11:52:25 +09:00
il f820e89cf6 refactor(roles): update binary application installation flow
update notes:
- keep set_cli_tools responsible only for console CLI tools
- download and install kopia from the kopia role
- download and install blocky from the blocky role
- download and install alloy from the alloy role
- reduce console artifact staging for service binaries
2026-05-09 10:46:29 +09:00
il a05951f883 fix(crowdsec): optimize whitelist expressions
update notes:
- add http_status and http_verb for each expressions (actual budget, immich, opencloud)
- fix crowdsec and issues documents
2026-05-07 10:32:11 +09:00
il b404a9e459 fix(crowdsec): update whitelist.yaml to prevent false positive
false positive:
- nextcloud thumbnail/preview 404 problem (crowdsecurity/http-probing)
2026-05-07 10:27:34 +09:00
il 3b4b56f53f fix(nftables): update fw nftables to allow vpn connection regardless of crowdsec ban 2026-05-07 09:22:49 +09:00
il f697715065 feat(sure): release sure (we-promise/sure)
deployment notes:
- let's try three of budget apps, actual budget, ezbookkeeping, and sure
2026-05-06 18:52:31 +09:00
153 changed files with 2010 additions and 720 deletions
+2
View File
@@ -2,6 +2,8 @@
data/bin/* data/bin/*
data/volumes/* data/volumes/*
data/images/* data/images/*
!data/images/containers
data/images/containers/*
docs/archives/textfiles/ docs/archives/textfiles/
docs/notes/* docs/notes/*
*.sql *.sql
+15 -6
View File
@@ -1,8 +1,17 @@
# ilnmors homelab README # ilnmors homelab README
This homelab project implements single-node On-premise IaaS system. The homelab contains virtual machines which are divided by their roles, such as private firewall, DNS, PKI, LDAP and database, SSO\(OIDC\). The standard domain is used to implement this system without specific vendors. All components are defined as code and initiated by IaC \(Ansible\) except hypervisor initial configuration. This homelab project implements single-node On-premise IaaS system. The homelab contains virtual machines which are divided by their roles, such as private firewall, DNS, PKI, LDAP and database, SSO(OIDC). The standard domain is used to implement this system without specific vendors. All components are defined as code and initiated by IaC (Ansible) except hypervisor initial configuration.
## RTO times ## RTO and RPO
### RPO
- Each backup guarantees 24 hours RPO
- DB dumps are backed up at 12:00 AM
- Stateful data in app vm is backed up at 03:00 AM
- The maximum inconsistency window between DB dumps and stateful data can be 27 hours.
- The different backup time.
### RTO
- Feb/25/2026 - Reprovisioning Hypervisor and vms - Feb/25/2026 - Reprovisioning Hypervisor and vms
- RTO: 1 hour 30 min - verified - RTO: 1 hour 30 min - verified
- Manual install and set vmm: 20 min - Manual install and set vmm: 20 min
@@ -15,12 +24,12 @@ This homelab project implements single-node On-premise IaaS system. The homelab
- Mar/5/2026 - Reprovisioning Hardware and Hypervisor and vms - Mar/5/2026 - Reprovisioning Hardware and Hypervisor and vms
- RTO: 2 hour 20 min - RTO: 2 hour 20 min
- console: 15min - verified - console: 15min - verified
- certificate: 0 min \(When it needs to be created, RTO will be 20 min) - not verified - certificate: 0 min (When it needs to be created, RTO will be 20 min) - not verified
- wireguard: 0 min \(When it needs to be created, RTO will be 1 min) - not verified - wireguard: 0 min (When it needs to be created, RTO will be 1 min) - not verified
- hypervisor\(+fw\): 45 min - verified - hypervisor(+fw): 45 min - verified
- switch: 1 min - verified - switch: 1 min - verified
- dsm: 30 min - verified - dsm: 30 min - verified
- kopia: 0 min \(When it needs to be created, RTO will be 10 min) - verified - kopia: 0 min (When it needs to be created, RTO will be 10 min) - verified
- Extra vms: 30 min - verified - Extra vms: 30 min - verified
- Etc: 30 min - Etc: 30 min
+2 -3
View File
@@ -33,7 +33,6 @@
tags: tags:
- "always" - "always"
- "init" - "init"
- "upgrade" - "site"
- "update" - "[service_name]"
# when: "'tags' is not in ansible_run_tags" # when: "'tags' is not in ansible_run_tags"
+19 -41
View File
@@ -1,6 +1,7 @@
--- ---
# Global vars # Global vars
ansible_ssh_private_key_file: "/etc/secrets/{{ hostvars['console']['node']['uid'] }}/id_console" ansible_ssh_private_key_file: "/etc/secrets/{{ hostvars['console']['node']['uid'] }}/id_console"
timezone: "Asia/Seoul"
# CA # CA
root_cert_filename: "ilnmors_root_ca.crt" root_cert_filename: "ilnmors_root_ca.crt"
@@ -66,7 +67,7 @@ services:
grafana: grafana:
domain: "grafana" domain: "grafana"
ports: ports:
http: "3000" http: "3000" # Infra server: Internal ports
subuid: "100471" subuid: "100471"
caddy: caddy:
ports: ports:
@@ -97,7 +98,7 @@ services:
public: "gitea" public: "gitea"
internal: "gitea.app" internal: "gitea.app"
ports: ports:
http: "3000" http: "3000" # App server: Public ports
subuid: "100999" subuid: "100999"
immich: immich:
domain: domain:
@@ -109,13 +110,6 @@ services:
immich-ml: immich-ml:
ports: ports:
http: "3003" http: "3003"
actualbudget:
domain:
public: "actualbudget"
internal: "actualbudget.app"
ports:
http: "5006"
subuid: "101000"
paperless: paperless:
domain: domain:
public: "paperless" public: "paperless"
@@ -124,20 +118,6 @@ services:
http: "8001" http: "8001"
redis: "6380" redis: "6380"
subuid: "100999" subuid: "100999"
vikunja:
domain:
public: "vikunja"
internal: "vikunja.app"
ports:
http: "3456"
subuid: "100999"
opencloud:
domain:
public: "opencloud"
internal: "opencloud.app"
ports:
http: "9200"
subuid: "100999"
manticore: manticore:
subuid: "100998" subuid: "100998"
affine: affine:
@@ -163,12 +143,13 @@ services:
ports: ports:
http: "9980" http: "9980"
subuid: "101000" subuid: "101000"
ezbookkeeping: sure:
domain: domain:
public: "budget" public: "sure"
internal: "budget.app" internal: "sure.app"
ports: ports:
http: "8003" http: "3001"
redis: "6383"
subuid: "100999" subuid: "100999"
version: version:
@@ -177,35 +158,32 @@ version:
step: "0.30.2" step: "0.30.2"
kopia: "0.22.3" kopia: "0.22.3"
blocky: "0.29.0" blocky: "0.29.0"
alloy: "1.13.0" alloy: "1.16.1"
containers: containers:
# common # common
caddy: "2.11.2" caddy: "2.11.2"
# infra # infra
step: "0.30.2" step: "0.30.2"
ldap: "v0.6.2" ldap: "v0.6.3"
x509-exporter: "3.21.0" x509-exporter: "4.1.0"
prometheus: "v3.9.1" prometheus: "v3.11.3"
loki: "3.6.5" loki: "3.7.1"
grafana: "12.3.3" grafana: "13.0.1"
## Postgresql ## Postgresql
postgresql: "18.2" postgresql: "18.3"
# For immich - https://github.com/immich-app/base-images/blob/main/postgres/versions.yaml # For immich - https://github.com/immich-app/base-images/blob/main/postgres/versions.yaml
# pgvector: "v0.8.1" # pgvector: "v0.8.1"
vectorchord: "0.5.3" vectorchord: "1.1.1"
# Auth # Auth
authelia: "4.39.19" authelia: "4.39.19"
# App # App
vaultwarden: "1.35.8" vaultwarden: "1.36.0"
gitea: "1.26.1" gitea: "1.26.1"
redis: "8.6.1" redis: "8.6.3"
immich: "v2.7.5" immich: "v2.7.5"
actualbudget: "26.3.0"
paperless: "2.20.15" paperless: "2.20.15"
vikunja: "2.2.2"
opencloud: "4.0.6"
manticore: "25.0.0" manticore: "25.0.0"
affine: "0.26.3" affine: "0.26.3"
nextcloud: "33.0.3" nextcloud: "33.0.3"
collabora: "25.04.9.4.1" collabora: "25.04.9.4.1"
ezbookkeeping: "1.4.0" sure: "0.7.0-hotfix.2"
+8 -32
View File
@@ -23,9 +23,9 @@
tags: ["always"] tags: ["always"]
tasks: tasks:
- name: Set timezone to Asia/Seoul - name: Set timezone
community.general.timezone: community.general.timezone:
name: Asia/Seoul name: "{{ timezone }}"
become: true become: true
tags: ["init", "timezone"] tags: ["init", "timezone"]
@@ -142,8 +142,8 @@
name: "common" name: "common"
tasks_from: "services/set_alloy" tasks_from: "services/set_alloy"
apply: apply:
tags: ["init", "update", "alloy"] tags: ["init", "alloy"]
tags: ["init", "update", "alloy"] tags: ["init", "alloy"]
- name: Set kopia - name: Set kopia
ansible.builtin.include_role: ansible.builtin.include_role:
@@ -185,14 +185,6 @@
tags: ["site", "immich"] tags: ["site", "immich"]
tags: ["site", "immich"] tags: ["site", "immich"]
- name: Set actual budget
ansible.builtin.include_role:
name: "app"
tasks_from: "services/set_actual-budget"
apply:
tags: ["site", "actual-budget"]
tags: ["site", "actual-budget"]
- name: Set paperless - name: Set paperless
ansible.builtin.include_role: ansible.builtin.include_role:
name: "app" name: "app"
@@ -201,22 +193,6 @@
tags: ["site", "paperless"] tags: ["site", "paperless"]
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: Set opencloud
ansible.builtin.include_role:
name: "app"
tasks_from: "services/set_opencloud"
apply:
tags: ["site", "opencloud"]
tags: ["site", "opencloud"]
- name: Set affine - name: Set affine
ansible.builtin.include_role: ansible.builtin.include_role:
name: "app" name: "app"
@@ -241,13 +217,13 @@
tags: ["site", "collabora"] tags: ["site", "collabora"]
tags: ["site", "collabora"] tags: ["site", "collabora"]
- name: Set ezbookkeeping - name: Set sure
ansible.builtin.include_role: ansible.builtin.include_role:
name: "app" name: "app"
tasks_from: "services/set_ezbookkeeping" tasks_from: "services/set_sure"
apply: apply:
tags: ["site", "ezbookkeeping"] tags: ["site", "sure"]
tags: ["site", "ezbookkeeping"] tags: ["site", "sure"]
- name: Flush handlers right now - name: Flush handlers right now
ansible.builtin.meta: "flush_handlers" ansible.builtin.meta: "flush_handlers"
+2 -2
View File
@@ -23,9 +23,9 @@
tags: ["always"] tags: ["always"]
tasks: tasks:
- name: Set timezone to Asia/Seoul - name: Set timezone
community.general.timezone: community.general.timezone:
name: Asia/Seoul name: "{{ timezone }}"
become: true become: true
tags: ["init", "timezone"] tags: ["init", "timezone"]
+10 -2
View File
@@ -24,9 +24,9 @@
tasks: tasks:
# init # init
- name: Set timezone to Asia/Seoul - name: Set timezone
community.general.timezone: community.general.timezone:
name: Asia/Seoul name: "{{ timezone }}"
become: true become: true
tags: ["init", "timezone"] tags: ["init", "timezone"]
@@ -122,3 +122,11 @@
apply: apply:
tags: ["init", "site", "tools"] tags: ["init", "site", "tools"]
tags: ["init", "site", "tools"] tags: ["init", "site", "tools"]
- name: Set kopia
ansible.builtin.include_role:
name: "common"
tasks_from: "services/set_kopia"
apply:
tags: ["init", "site", "kopia"]
tags: ["init", "site", "kopia"]
+4 -4
View File
@@ -23,9 +23,9 @@
tags: ["always"] tags: ["always"]
tasks: tasks:
- name: Set timezone to Asia/Seoul - name: Set timezone
community.general.timezone: community.general.timezone:
name: Asia/Seoul name: "{{ timezone }}"
become: true become: true
tags: ["init", "timezone"] tags: ["init", "timezone"]
@@ -162,8 +162,8 @@
name: "fw" name: "fw"
tasks_from: "services/set_bind" tasks_from: "services/set_bind"
apply: apply:
tags: ["init", "update", "bind"] tags: ["init", "bind"]
tags: ["init", "update", "bind"] tags: ["init", "bind"]
- name: Set blocky - name: Set blocky
ansible.builtin.include_role: ansible.builtin.include_role:
+2 -2
View File
@@ -23,9 +23,9 @@
tags: ["always"] tags: ["always"]
tasks: tasks:
- name: Set timezone to Asia/Seoul - name: Set timezone
community.general.timezone: community.general.timezone:
name: Asia/Seoul name: "{{ timezone }}"
become: true become: true
tags: ["init", "timezone"] tags: ["init", "timezone"]
+2 -2
View File
@@ -30,9 +30,9 @@
tags: ["always"] tags: ["always"]
tasks: tasks:
# init # init
- name: Set timezone to Asia/Seoul - name: Set timezone
community.general.timezone: community.general.timezone:
name: Asia/Seoul name: "{{ timezone }}"
become: true become: true
tags: ["init", "timezone"] tags: ["init", "timezone"]
+6 -37
View File
@@ -43,17 +43,6 @@
listen: "notification_restart_immich-ml" listen: "notification_restart_immich-ml"
ignore_errors: true # noqa: ignore-errors ignore_errors: true # noqa: ignore-errors
- name: Restart actual-budget
ansible.builtin.systemd:
name: "actual-budget.service"
state: "restarted"
enabled: true
scope: "user"
daemon_reload: true
changed_when: false
listen: "notification_restart_actual-budget"
ignore_errors: true # noqa: ignore-errors
- name: Restart paperless - name: Restart paperless
ansible.builtin.systemd: ansible.builtin.systemd:
name: "paperless.service" name: "paperless.service"
@@ -65,29 +54,6 @@
listen: "notification_restart_paperless" listen: "notification_restart_paperless"
ignore_errors: true # noqa: ignore-errors 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
- name: Restart opencloud
ansible.builtin.systemd:
name: "opencloud.service"
state: "restarted"
enabled: true
daemon_reload: true
scope: "user"
when: is_opencloud_init.stat.exists
changed_when: false
listen: "notification_restart_opencloud"
ignore_errors: true # noqa: ignore-errors
- name: Restart affine - name: Restart affine
ansible.builtin.systemd: ansible.builtin.systemd:
name: "affine.service" name: "affine.service"
@@ -123,13 +89,16 @@
listen: "notification_restart_collabora" listen: "notification_restart_collabora"
ignore_errors: true # noqa: ignore-errors ignore_errors: true # noqa: ignore-errors
- name: Restart ezbookkeeping - name: Restart sure
ansible.builtin.systemd: ansible.builtin.systemd:
name: "ezbookkeeping.service" name: "{{ item }}"
state: "restarted" state: "restarted"
enabled: true enabled: true
scope: "user" scope: "user"
daemon_reload: true daemon_reload: true
loop:
- "sure-web.service"
- "sure-worker.service"
changed_when: false changed_when: false
listen: "notification_restart_ezbookkeeping" listen: "notification_restart_sure"
ignore_errors: true # noqa: ignore-errors ignore_errors: true # noqa: ignore-errors
@@ -161,3 +161,38 @@
enabled: true enabled: true
daemon_reload: true daemon_reload: true
scope: "user" scope: "user"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/{{ item.file }}.tar"
loop:
- image: "docker.io/manticoresearch/manticore:{{ version['containers']['manticore'] }}"
file: "docker.io_manticoresearch_manticore_{{ version['containers']['manticore'] }}"
- image: "docker.io/library/redis:{{ version['containers']['redis'] }}"
file: "docker.io_library_redis_{{ version['containers']['redis'] }}"
- image: "ghcr.io/toeverything/affine:{{ version['containers']['affine'] }}"
file: "ghcr.io_toeverything_affine_{{ version['containers']['affine'] }}"
loop_control:
label: "{{ item.file }}"
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 }}"
@@ -15,3 +15,23 @@
enabled: true enabled: true
daemon_reload: true daemon_reload: true
scope: "user" scope: "user"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/docker.io_collabora_code_{{ version['containers']['collabora'] }}.tar"
register: container_archive_images
- name: Save container archive images
containers.podman.podman_save:
image:
- "docker.io/collabora/code:{{ version['containers']['collabora'] }}"
dest: "{{ node['home_path'] }}/archives/containers/docker.io_collabora_code_{{ version['containers']['collabora'] }}.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_collabora_code_{{ version['containers']['collabora'] }}.tar"
dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/"
flat: true
@@ -49,3 +49,23 @@
enabled: true enabled: true
daemon_reload: true daemon_reload: true
scope: "user" scope: "user"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/docker.io_gitea_gitea_{{ version['containers']['gitea'] }}.tar"
register: container_archive_images
- name: Save container archive images
containers.podman.podman_save:
image:
- "docker.io/gitea/gitea:{{ version['containers']['gitea'] }}"
dest: "{{ node['home_path'] }}/archives/containers/docker.io_gitea_gitea_{{ version['containers']['gitea'] }}.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_gitea_gitea_{{ version['containers']['gitea'] }}.tar"
dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/"
flat: true
@@ -118,3 +118,38 @@
enabled: true enabled: true
daemon_reload: true daemon_reload: true
scope: "user" scope: "user"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/{{ item.file }}.tar"
loop:
- image: "docker.io/library/redis:{{ version['containers']['redis'] }}"
file: "docker.io_library_redis_{{ version['containers']['redis'] }}"
- image: "ghcr.io/immich-app/immich-machine-learning:{{ version['containers']['immich'] }}-openvino"
file: "ghcr.io_immich-app_immich-machine-learning_{{ version['containers']['immich'] }}-openvino"
- image: "ghcr.io/immich-app/immich-server:{{ version['containers']['immich'] }}"
file: "ghcr.io_immich-app_immich-server_{{ version['containers']['immich'] }}"
loop_control:
label: "{{ item.file }}"
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 }}"
@@ -174,3 +174,36 @@
enabled: true enabled: true
daemon_reload: true daemon_reload: true
scope: "user" scope: "user"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/{{ item.file }}.tar"
loop:
- image: "docker.io/library/redis:{{ version['containers']['redis'] }}"
file: "docker.io_library_redis_{{ version['containers']['redis'] }}"
- image: "docker.io/library/nextcloud:{{ version['containers']['nextcloud'] }}"
file: "docker.io_library_nextcloud_{{ version['containers']['nextcloud'] }}"
loop_control:
label: "{{ item.file }}"
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 }}"
@@ -57,8 +57,16 @@
- "data/containers/paperless/consume" - "data/containers/paperless/consume"
- "containers/paperless" - "containers/paperless"
- "containers/paperless/ssl" - "containers/paperless/ssl"
- "containers/paperless/build"
become: true become: true
- name: Deploy containerfile for build
ansible.builtin.template:
src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/paperless/build/paperless.containerfile.j2"
dest: "{{ node['home_path'] }}/containers/paperless/build/Containerfile"
owner: "{{ ansible_user }}"
group: "svadmins"
mode: "0640"
- name: Deploy root certificate - name: Deploy root certificate
ansible.builtin.copy: ansible.builtin.copy:
@@ -72,6 +80,18 @@
notify: "notification_restart_paperless" notify: "notification_restart_paperless"
no_log: true no_log: true
- name: Build paperless container image
containers.podman.podman_image:
name: "{{ domain['internal'] }}/{{ node['name'] }}/paperless-ngx"
# check tags from container file
tag: "{{ version['containers']['paperless'] }}"
state: "build"
path: "{{ node['home_path'] }}/containers/paperless/build"
- name: Prune paperless dangling images
containers.podman.podman_prune:
image: true
- name: Register secret value to podman secret - name: Register secret value to podman secret
containers.podman.podman_secret: containers.podman.podman_secret:
name: "{{ item.name }}" name: "{{ item.name }}"
@@ -122,3 +142,36 @@
enabled: true enabled: true
daemon_reload: true daemon_reload: true
scope: "user" scope: "user"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/{{ item.file }}.tar"
loop:
- image: "docker.io/library/redis:{{ version['containers']['redis'] }}"
file: "docker.io_library_redis_{{ version['containers']['redis'] }}"
- image: "{{ domain['internal'] }}/{{ node['name'] }}/paperless-ngx:{{ version['containers']['paperless'] }}"
file: "{{ domain['internal'] }}_{{ node['name'] }}_paperless-ngx_{{ version['containers']['paperless'] }}"
loop_control:
label: "{{ item.file }}"
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 }}"
@@ -0,0 +1,143 @@
---
- name: Set redis service name
ansible.builtin.set_fact:
redis_service: "sure"
- name: Create redis_sure directory
ansible.builtin.file:
path: "{{ node['home_path'] }}/{{ item }}"
state: "directory"
owner: "{{ services['redis']['subuid'] }}"
group: "svadmins"
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: "svadmins"
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: "svadmins"
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 sure directory
ansible.builtin.file:
path: "{{ node['home_path'] }}/{{ item }}"
state: "directory"
owner: "{{ services['sure']['subuid'] }}"
group: "svadmins"
mode: "0770"
loop:
- "data/containers/sure"
- "data/containers/sure/storage"
- "containers/sure"
- "containers/sure/ssl"
become: true
- name: Deploy root certificate
ansible.builtin.copy:
content: |
{{ hostvars['console']['ca']['root']['crt'] }}
dest: "{{ node['home_path'] }}/containers/sure/ssl/{{ root_cert_filename }}"
owner: "{{ services['sure']['subuid'] }}"
group: "svadmins"
mode: "0440"
become: true
notify: "notification_restart_sure"
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: "SURE_SECRET_KEY_BASE"
value: "{{ hostvars['console']['sure']['session_secret'] }}"
- name: "SURE_POSTGRES_PASSWORD"
value: "{{ hostvars['console']['postgresql']['password']['sure'] }}"
- name: "SURE_OIDC_CLIENT_SECRET"
value: "{{ hostvars['console']['sure']['oidc']['secret'] }}"
notify: "notification_restart_sure"
no_log: true
- name: Deploy sure.container file
ansible.builtin.template:
src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/sure/{{ item }}.j2"
dest: "{{ node['home_path'] }}/.config/containers/systemd/{{ item }}"
owner: "{{ ansible_user }}"
group: "svadmins"
mode: "0644"
loop:
- "sure-web.container"
- "sure-worker.container"
notify: "notification_restart_sure"
- name: Enable sure.service
ansible.builtin.systemd:
name: "{{ item }}"
state: "started"
enabled: true
daemon_reload: true
scope: "user"
loop:
- "sure-web.service"
- "sure-worker.service"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/{{ item.file }}.tar"
loop:
- image: "docker.io/library/redis:{{ version['containers']['redis'] }}"
file: "docker.io_library_redis_{{ version['containers']['redis'] }}"
- image: "ghcr.io/we-promise/sure:{{ version['containers']['sure'] }}"
file: "ghcr.io_we-promise_sure_{{ version['containers']['sure'] }}"
loop_control:
label: "{{ item.file }}"
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 }}"
@@ -55,3 +55,23 @@
enabled: true enabled: true
daemon_reload: true daemon_reload: true
scope: "user" scope: "user"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/docker.io_vaultwarden_server_{{ version['containers']['vaultwarden'] }}.tar"
register: container_archive_images
- name: Save container archive images
containers.podman.podman_save:
image:
- "docker.io/vaultwarden/server:{{ version['containers']['vaultwarden'] }}"
dest: "{{ node['home_path'] }}/archives/containers/docker.io_vaultwarden_server_{{ version['containers']['vaultwarden'] }}.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_vaultwarden_server_{{ version['containers']['vaultwarden'] }}.tar"
dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/"
flat: true
@@ -76,3 +76,23 @@
enabled: true enabled: true
daemon_reload: true daemon_reload: true
scope: "user" scope: "user"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/docker.io_authelia_authelia_{{ version['containers']['authelia'] }}.tar"
register: container_archive_images
- name: Save container archive images
containers.podman.podman_save:
image:
- "docker.io/authelia/authelia:{{ version['containers']['authelia'] }}"
dest: "{{ node['home_path'] }}/archives/containers/docker.io_authelia_authelia_{{ version['containers']['authelia'] }}.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_authelia_authelia_{{ version['containers']['authelia'] }}.tar"
dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/"
flat: true
@@ -5,9 +5,10 @@
- hardware - hardware
become: true become: true
- name: Deploy alloy deb file (x86_64) - name: Download alloy deb file (x86_64)
ansible.builtin.copy: ansible.builtin.get_url:
src: "{{ hostvars['console']['node']['data_path'] }}/bin/alloy-{{ version['packages']['alloy'] }}-amd64.deb" url: "https://github.com/grafana/alloy/releases/download/v{{ version['packages']['alloy'] }}/\
alloy-{{ version['packages']['alloy'] }}-1.amd64.deb"
dest: "/var/cache/apt/archives/alloy-{{ version['packages']['alloy'] }}.deb" dest: "/var/cache/apt/archives/alloy-{{ version['packages']['alloy'] }}.deb"
owner: "root" owner: "root"
group: "root" group: "root"
@@ -15,9 +16,10 @@
become: true become: true
when: ansible_facts['architecture'] == "x86_64" when: ansible_facts['architecture'] == "x86_64"
- name: Deploy alloy deb file (aarch64) - name: Download alloy deb file (aarch64)
ansible.builtin.copy: ansible.builtin.get_url:
src: "{{ hostvars['console']['node']['data_path'] }}/bin/alloy-{{ version['packages']['alloy'] }}-arm64.deb" url: "https://github.com/grafana/alloy/releases/download/v{{ version['packages']['alloy'] }}/\
alloy-{{ version['packages']['alloy'] }}-1.arm64.deb"
dest: "/var/cache/apt/archives/alloy-{{ version['packages']['alloy'] }}.deb" dest: "/var/cache/apt/archives/alloy-{{ version['packages']['alloy'] }}.deb"
owner: "root" owner: "root"
group: "root" group: "root"
@@ -30,6 +32,7 @@
deb: "/var/cache/apt/archives/alloy-{{ version['packages']['alloy'] }}.deb" deb: "/var/cache/apt/archives/alloy-{{ version['packages']['alloy'] }}.deb"
state: "present" state: "present"
become: true become: true
notify: "notification_restart_alloy"
- name: Deploy alloy config - name: Deploy alloy config
ansible.builtin.template: ansible.builtin.template:
@@ -71,3 +74,10 @@
enabled: true enabled: true
daemon_reload: true daemon_reload: true
become: true become: true
- name: Fetch deb bin file
ansible.builtin.fetch:
src: "/var/cache/apt/archives/alloy-{{ version['packages']['alloy'] }}.deb"
dest: "{{ hostvars['console']['node']['data_path'] }}/bin/"
flat: true
become: true
@@ -97,3 +97,23 @@
enabled: true enabled: true
daemon_reload: true daemon_reload: true
scope: "user" scope: "user"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/{{ domain['internal'] }}_{{ node['name'] }}_caddy_{{ version['containers']['caddy'] }}.tar"
register: container_archive_images
- name: Save container archive images
containers.podman.podman_save:
image:
- "{{ domain['internal'] }}/{{ node['name'] }}/caddy:{{ version['containers']['caddy'] }}"
dest: "{{ node['home_path'] }}/archives/containers/{{ domain['internal'] }}_{{ node['name'] }}_caddy_{{ version['containers']['caddy'] }}.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/{{ domain['internal'] }}_{{ node['name'] }}_caddy_{{ version['containers']['caddy'] }}.tar"
dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/"
flat: true
@@ -5,34 +5,36 @@
- hardware - hardware
become: true become: true
- name: Check kopia installation
ansible.builtin.shell: |
command -v kopia
changed_when: false
failed_when: false
register: "is_kopia_installed"
ignore_errors: true
- name: Set console kopia - name: Set console kopia
when: node['name'] == 'console' when: node['name'] == 'console'
block: block:
- name: Apply cli tools (x86_64) - name: Download kopia
ansible.builtin.get_url:
url: "https://github.com/kopia/kopia/releases/download/v{{ version['packages']['kopia'] }}/\
kopia_{{ version['packages']['kopia'] }}_linux_{{ item }}.deb"
dest: "{{ node['data_path'] }}/bin/kopia-{{ version['packages']['kopia'] }}-{{ item }}.deb"
owner: "{{ ansible_user }}"
group: "svadmins"
mode: "0600"
loop:
- "amd64"
- "arm64"
- name: Install kopia (x86_64)
ansible.builtin.apt: ansible.builtin.apt:
deb: "{{ node['data_path'] }}/bin/kopia-{{ version['packages']['kopia'] }}-amd64.deb" deb: "{{ node['data_path'] }}/bin/kopia-{{ version['packages']['kopia'] }}-amd64.deb"
state: "present" state: "present"
become: true become: true
when: when: ansible_facts['architecture'] == "x86_64"
- ansible_facts['architecture'] == "x86_64"
- is_kopia_installed.rc != 0 - name: Install kopia (aarch64)
- name: Apply cli tools (aarch64)
ansible.builtin.apt: ansible.builtin.apt:
deb: "{{ node['data_path'] }}/bin/kopia-{{ version['packages']['kopia'] }}-arm64.deb" deb: "{{ node['data_path'] }}/bin/kopia-{{ version['packages']['kopia'] }}-arm64.deb"
state: "present" state: "present"
become: true become: true
when: when: ansible_facts['architecture'] == "aarch64"
- ansible_facts['architecture'] == "aarch64"
- is_kopia_installed.rc != 0 - name: Connect console kopia server
- name: Connect kopia server
environment: environment:
KOPIA_PASSWORD: "{{ hostvars['console']['kopia']['user']['console'] }}" KOPIA_PASSWORD: "{{ hostvars['console']['kopia']['user']['console'] }}"
ansible.builtin.shell: | ansible.builtin.shell: |
@@ -51,30 +53,36 @@
- name: Set kopia uid - name: Set kopia uid
ansible.builtin.set_fact: ansible.builtin.set_fact:
kopia_uid: 951 kopia_uid: 951
- name: Deploy kopia deb file (x86_64)
ansible.builtin.copy: - name: Download kopia deb file (x86_64)
src: "{{ hostvars['console']['node']['data_path'] }}/bin/kopia-{{ version['packages']['kopia'] }}-amd64.deb" ansible.builtin.get_url:
url: "https://github.com/kopia/kopia/releases/download/v{{ version['packages']['kopia'] }}/\
kopia_{{ version['packages']['kopia'] }}_linux_amd64.deb"
dest: "/var/cache/apt/archives/kopia-{{ version['packages']['kopia'] }}.deb" dest: "/var/cache/apt/archives/kopia-{{ version['packages']['kopia'] }}.deb"
owner: "root" owner: "root"
group: "root" group: "root"
mode: "0644" mode: "0644"
become: true become: true
when: ansible_facts['architecture'] == "x86_64" when: ansible_facts['architecture'] == "x86_64"
- name: Deploy kopia deb file (aarch64)
ansible.builtin.copy: - name: Download kopia deb file (aarch64)
src: "{{ hostvars['console']['node']['data_path'] }}/bin/kopia-{{ version['packages']['kopia'] }}-arm64.deb" ansible.builtin.get_url:
url: "https://github.com/kopia/kopia/releases/download/v{{ version['packages']['kopia'] }}/\
kopia_{{ version['packages']['kopia'] }}_linux_arm64.deb"
dest: "/var/cache/apt/archives/kopia-{{ version['packages']['kopia'] }}.deb" dest: "/var/cache/apt/archives/kopia-{{ version['packages']['kopia'] }}.deb"
owner: "root" owner: "root"
group: "root" group: "root"
mode: "0644" mode: "0644"
become: true become: true
when: ansible_facts['architecture'] == "aarch64" when: ansible_facts['architecture'] == "aarch64"
- name: Create kopia group - name: Create kopia group
ansible.builtin.group: ansible.builtin.group:
name: "kopia" name: "kopia"
gid: "{{ kopia_uid }}" gid: "{{ kopia_uid }}"
state: "present" state: "present"
become: true become: true
- name: Create kopia user - name: Create kopia user
ansible.builtin.user: ansible.builtin.user:
name: "kopia" name: "kopia"
@@ -85,6 +93,7 @@
comment: "Kopia backup User" comment: "Kopia backup User"
state: "present" state: "present"
become: true become: true
- name: Create kopia directory - name: Create kopia directory
ansible.builtin.file: ansible.builtin.file:
path: "{{ item.name }}" path: "{{ item.name }}"
@@ -101,12 +110,13 @@
mode: "0700" mode: "0700"
become: true become: true
no_log: true no_log: true
- name: Install kopia - name: Install kopia
ansible.builtin.apt: ansible.builtin.apt:
deb: "/var/cache/apt/archives/kopia-{{ version['packages']['kopia'] }}.deb" deb: "/var/cache/apt/archives/kopia-{{ version['packages']['kopia'] }}.deb"
state: "present" state: "present"
become: true become: true
when: is_kopia_installed.rc != 0
- name: Deploy kopia env - name: Deploy kopia env
ansible.builtin.template: ansible.builtin.template:
src: "{{ hostvars['console']['node']['config_path'] }}/services/systemd/common/kopia/kopia.env.j2" src: "{{ hostvars['console']['node']['config_path'] }}/services/systemd/common/kopia/kopia.env.j2"
@@ -116,6 +126,7 @@
mode: "0400" mode: "0400"
become: true become: true
no_log: true no_log: true
- name: Deploy kopia service files - name: Deploy kopia service files
ansible.builtin.template: ansible.builtin.template:
src: "{{ hostvars['console']['node']['config_path'] }}/services/systemd/common/kopia/{{ item }}.j2" src: "{{ hostvars['console']['node']['config_path'] }}/services/systemd/common/kopia/{{ item }}.j2"
@@ -128,6 +139,7 @@
- "kopia-backup.service" - "kopia-backup.service"
- "kopia-backup.timer" - "kopia-backup.timer"
become: true become: true
- name: Enable auto kopia rules update - name: Enable auto kopia rules update
ansible.builtin.systemd: ansible.builtin.systemd:
name: "kopia-backup.timer" name: "kopia-backup.timer"
@@ -24,6 +24,17 @@
mode: "0770" mode: "0770"
when: node['name'] == "app" when: node['name'] == "app"
- name: Create container image archive directory
ansible.builtin.file:
path: "{{ item }}"
owner: "{{ ansible_user }}"
group: "svadmins"
state: "directory"
mode: "0700"
loop:
- "{{ node['home_path'] }}/archives"
- "{{ node['home_path'] }}/archives/containers"
- name: Install podman and reset ssh connection for initiating - name: Install podman and reset ssh connection for initiating
when: is_podman_installed.rc != 0 when: is_podman_installed.rc != 0
become: true become: true
-7
View File
@@ -1,8 +1 @@
--- ---
- name: Register font
ansible.builtin.shell: |
fc-cache -f -v
become: true
changed_when: false
listen: "notification_update_font"
ignore_errors: true # noqa: ignore-errors
@@ -49,42 +49,6 @@
- "amd64" - "amd64"
- "arm64" - "arm64"
- name: Download kopia
ansible.builtin.get_url:
url: "https://github.com/kopia/kopia/releases/download/v{{ version['packages']['kopia'] }}/\
kopia_{{ version['packages']['kopia'] }}_linux_{{ item }}.deb"
dest: "{{ node['data_path'] }}/bin/kopia-{{ version['packages']['kopia'] }}-{{ item }}.deb"
owner: "{{ ansible_user }}"
group: "svadmins"
mode: "0600"
loop:
- "amd64"
- "arm64"
- name: Download blocky
ansible.builtin.get_url:
url: "https://github.com/0xERR0R/blocky/releases/download/v{{ version['packages']['blocky'] }}/\
blocky_v{{ version['packages']['blocky'] }}_Linux_{{ item }}.tar.gz"
dest: "{{ node['data_path'] }}/bin/blocky-{{ version['packages']['blocky'] }}-{{ item }}.tar.gz"
owner: "{{ ansible_user }}"
group: "svadmins"
mode: "0600"
loop:
- "x86_64"
- "arm64"
- name: Download alloy
ansible.builtin.get_url:
url: "https://github.com/grafana/alloy/releases/download/v{{ version['packages']['alloy'] }}/\
alloy-{{ version['packages']['alloy'] }}-1.{{ item }}.deb"
dest: "{{ node['data_path'] }}/bin/alloy-{{ version['packages']['alloy'] }}-{{ item }}.deb"
owner: "{{ ansible_user }}"
group: "svadmins"
mode: "0600"
loop:
- "amd64"
- "arm64"
- name: Apply cli tools (x86_64) - name: Apply cli tools (x86_64)
ansible.builtin.apt: ansible.builtin.apt:
deb: "{{ node['data_path'] }}/bin/{{ item }}" deb: "{{ node['data_path'] }}/bin/{{ item }}"
@@ -92,7 +56,6 @@
loop: loop:
- "sops-{{ version['packages']['sops'] }}-amd64.deb" - "sops-{{ version['packages']['sops'] }}-amd64.deb"
- "step-{{ version['packages']['step'] }}-amd64.deb" - "step-{{ version['packages']['step'] }}-amd64.deb"
- "kopia-{{ version['packages']['kopia'] }}-amd64.deb"
become: true become: true
when: ansible_facts['architecture'] == "x86_64" when: ansible_facts['architecture'] == "x86_64"
@@ -103,6 +66,5 @@
loop: loop:
- "sops-{{ version['packages']['sops'] }}-arm64.deb" - "sops-{{ version['packages']['sops'] }}-arm64.deb"
- "step-{{ version['packages']['step'] }}-arm64.deb" - "step-{{ version['packages']['step'] }}-arm64.deb"
- "kopia-{{ version['packages']['kopia'] }}-arm64.deb"
become: true become: true
when: ansible_facts['architecture'] == "aarch64" when: ansible_facts['architecture'] == "aarch64"
+32 -16
View File
@@ -23,7 +23,7 @@
state: "present" state: "present"
become: true become: true
- name: Create blocky etc directory - name: Create blocky directory
ansible.builtin.file: ansible.builtin.file:
path: "{{ item }}" path: "{{ item }}"
owner: "blocky" owner: "blocky"
@@ -31,28 +31,38 @@
mode: "0750" mode: "0750"
state: "directory" state: "directory"
loop: loop:
- "/home/blocky"
- "/home/blocky/bin"
- "/etc/blocky" - "/etc/blocky"
- "/etc/blocky/ssl" - "/etc/blocky/ssl"
become: true become: true
- name: Deploy blocky binary file (x86_64) - name: Download blocky (x86_64)
ansible.builtin.unarchive: ansible.builtin.get_url:
src: "{{ hostvars['console']['node']['data_path'] }}/bin/blocky-{{ version['packages']['blocky'] }}-x86_64.tar.gz" url: "https://github.com/0xERR0R/blocky/releases/download/v{{ version['packages']['blocky'] }}/\
dest: "/usr/local/bin/" blocky_v{{ version['packages']['blocky'] }}_Linux_x86_64.tar.gz"
owner: "root" dest: "/home/blocky/bin/blocky-{{ version['packages']['blocky'] }}.tar.gz"
group: "root" owner: "blocky"
mode: "0755" group: "blocky"
extra_opts: mode: "0600"
- "--strip-components=0"
- "--wildcards"
- "blocky"
become: true become: true
when: ansible_facts['architecture'] == "x86_64" when: ansible_facts['architecture'] == "x86_64"
notify: "notification_restart_blocky"
- name: Deploy blocky binary file (aarch64) - name: Download blocky (aarch64)
ansible.builtin.get_url:
url: "https://github.com/0xERR0R/blocky/releases/download/v{{ version['packages']['blocky'] }}/\
blocky_v{{ version['packages']['blocky'] }}_Linux_arm64.tar.gz"
dest: "/home/blocky/bin/blocky-{{ version['packages']['blocky'] }}.tar.gz"
owner: "blocky"
group: "blocky"
mode: "0600"
become: true
when: ansible_facts['architecture'] == "aarch64"
- name: Deploy blocky binary file
ansible.builtin.unarchive: ansible.builtin.unarchive:
src: "{{ hostvars['console']['node']['data_path'] }}/bin/blocky-{{ version['packages']['blocky'] }}-arm64.tar.gz" src: "/home/blocky/bin/blocky-{{ version['packages']['blocky'] }}.tar.gz"
remote_src: true
dest: "/usr/local/bin/" dest: "/usr/local/bin/"
owner: "root" owner: "root"
group: "root" group: "root"
@@ -62,7 +72,6 @@
- "--wildcards" - "--wildcards"
- "blocky" - "blocky"
become: true become: true
when: ansible_facts['architecture'] == "aarch64"
notify: "notification_restart_blocky" notify: "notification_restart_blocky"
- name: Deploy blocky config - name: Deploy blocky config
@@ -115,3 +124,10 @@
enabled: true enabled: true
daemon_reload: true daemon_reload: true
become: true become: true
- name: Fetch deb bin file
ansible.builtin.fetch:
src: "/home/blocky/bin/blocky-{{ version['packages']['blocky'] }}.tar.gz"
dest: "{{ hostvars['console']['node']['data_path'] }}/bin/"
flat: true
become: true
@@ -78,3 +78,23 @@
enabled: true enabled: true
daemon_reload: true daemon_reload: true
scope: "user" scope: "user"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/docker.io_smallstep_step-ca_{{ version['containers']['step'] }}.tar"
register: container_archive_images
- name: Save container archive images
containers.podman.podman_save:
image:
- "docker.io/smallstep/step-ca:{{ version['containers']['step'] }}"
dest: "{{ node['home_path'] }}/archives/containers/docker.io_smallstep_step-ca_{{ version['containers']['step'] }}.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_smallstep_step-ca_{{ version['containers']['step'] }}.tar"
dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/"
flat: true
@@ -83,3 +83,23 @@
enabled: true enabled: true
daemon_reload: true daemon_reload: true
scope: "user" scope: "user"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/docker.io_grafana_grafana_{{ version['containers']['grafana'] }}.tar"
register: container_archive_images
- name: Save container archive images
containers.podman.podman_save:
image:
- "docker.io/grafana/grafana:{{ version['containers']['grafana'] }}"
dest: "{{ node['home_path'] }}/archives/containers/docker.io_grafana_grafana_{{ version['containers']['grafana'] }}.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_grafana_grafana_{{ version['containers']['grafana'] }}.tar"
dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/"
flat: true
@@ -75,7 +75,7 @@
rm: true rm: true
detach: false detach: false
env: env:
TZ: "Asia/Seoul" TZ: "{{ timezone }}"
LLDAP_LDAP_BASE_DN: "{{ domain['dc'] }}" LLDAP_LDAP_BASE_DN: "{{ domain['dc'] }}"
secrets: secrets:
- "LLDAP_DATABASE_URL,type=env" - "LLDAP_DATABASE_URL,type=env"
@@ -108,3 +108,23 @@
enabled: true enabled: true
daemon_reload: true daemon_reload: true
scope: "user" scope: "user"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/docker.io_lldap_lldap_{{ version['containers']['ldap'] }}.tar"
register: container_archive_images
- name: Save container archive images
containers.podman.podman_save:
image:
- "docker.io/lldap/lldap:{{ version['containers']['ldap'] }}"
dest: "{{ node['home_path'] }}/archives/containers/docker.io_lldap_lldap_{{ version['containers']['ldap'] }}.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_lldap_lldap_{{ version['containers']['ldap'] }}.tar"
dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/"
flat: true
@@ -64,3 +64,23 @@
enabled: true enabled: true
daemon_reload: true daemon_reload: true
scope: "user" scope: "user"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/docker.io_grafana_loki_{{ version['containers']['loki'] }}.tar"
register: container_archive_images
- name: Save container archive images
containers.podman.podman_save:
image:
- "docker.io/grafana/loki:{{ version['containers']['loki'] }}"
dest: "{{ node['home_path'] }}/archives/containers/docker.io_grafana_loki_{{ version['containers']['loki'] }}.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_grafana_loki_{{ version['containers']['loki'] }}.tar"
dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/"
flat: true
@@ -9,10 +9,9 @@
- "gitea" - "gitea"
- "immich" - "immich"
- "paperless" - "paperless"
- "vikunja"
- "affine" - "affine"
- "nextcloud" - "nextcloud"
- "ezbookkeeping" - "sure"
- name: Create postgresql directory - name: Create postgresql directory
ansible.builtin.file: ansible.builtin.file:
@@ -173,3 +172,29 @@
daemon_reload: true daemon_reload: true
scope: "user" scope: "user"
loop: "{{ connected_services }}" loop: "{{ connected_services }}"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/\
{{ domain['internal'] }}_{{ node['name'] }}_postgres_\
pg{{ version['containers']['postgresql'] }}-vectorchord{{ version['containers']['vectorchord'] }}.tar"
register: container_archive_images
- name: Save container archive images
containers.podman.podman_save:
image:
- "{{ domain['internal'] }}/{{ node['name'] }}/postgres:pg{{ version['containers']['postgresql'] }}-vectorchord{{ version['containers']['vectorchord'] }}"
dest: "{{ node['home_path'] }}/archives/containers/\
{{ domain['internal'] }}_{{ node['name'] }}_postgres_\
pg{{ version['containers']['postgresql'] }}-vectorchord{{ version['containers']['vectorchord'] }}.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/\
{{ domain['internal'] }}_{{ node['name'] }}_postgres_\
pg{{ version['containers']['postgresql'] }}-vectorchord{{ version['containers']['vectorchord'] }}.tar"
dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/"
flat: true
@@ -68,3 +68,23 @@
enabled: true enabled: true
daemon_reload: true daemon_reload: true
scope: "user" scope: "user"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/docker.io_prom_prometheus_{{ version['containers']['prometheus'] }}.tar"
register: container_archive_images
- name: Save container archive images
containers.podman.podman_save:
image:
- "docker.io/prom/prometheus:{{ version['containers']['prometheus'] }}"
dest: "{{ node['home_path'] }}/archives/containers/docker.io_prom_prometheus_{{ version['containers']['prometheus'] }}.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_prom_prometheus_{{ version['containers']['prometheus'] }}.tar"
dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/"
flat: true
@@ -8,9 +8,20 @@
mode: "0770" mode: "0770"
loop: loop:
- "x509-exporter" - "x509-exporter"
- "x509-exporter/config"
- "x509-exporter/certs" - "x509-exporter/certs"
become: true become: true
- name: Deploy config.yaml
ansible.builtin.copy:
src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/infra/x509-exporter/config/config.yaml"
dest: "{{ node['home_path'] }}/containers/x509-exporter/config/config.yaml"
owner: "{{ services['x509-exporter']['subuid'] }}"
group: "svadmins"
mode: "0440"
become: true
notify: "notification_restart_x509-exporter"
- name: Deploy certificates - name: Deploy certificates
ansible.builtin.copy: ansible.builtin.copy:
content: | content: |
@@ -57,3 +68,23 @@
enabled: true enabled: true
daemon_reload: true daemon_reload: true
scope: "user" scope: "user"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/docker.io_enix_x509-certificate-exporter_{{ version['containers']['x509-exporter'] }}.tar"
register: container_archive_images
- name: Save container archive images
containers.podman.podman_save:
image:
- "docker.io/enix/x509-certificate-exporter:{{ version['containers']['x509-exporter'] }}"
dest: "{{ node['home_path'] }}/archives/containers/docker.io_enix_x509-certificate-exporter_{{ version['containers']['x509-exporter'] }}.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_enix_x509-certificate-exporter_{{ version['containers']['x509-exporter'] }}.tar"
dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/"
flat: true
+2
View File
@@ -82,6 +82,8 @@ table inet filter {
chain global { chain global {
# invalid packets # invalid packets
ct state invalid drop comment "deny invalid connection" ct state invalid drop comment "deny invalid connection"
# VPN connection exception handling
udp dport $PORTS_VPN return comment "return vpn connection to input and forward chain"
# crowdsec # crowdsec
ip saddr @crowdsec-blacklists counter drop comment "deny all crowdsec blacklist" ip saddr @crowdsec-blacklists counter drop comment "deny all crowdsec blacklist"
ip6 saddr @crowdsec6-blacklists counter drop comment "deny all ipv6 crowdsec blacklist" ip6 saddr @crowdsec6-blacklists counter drop comment "deny all ipv6 crowdsec blacklist"
+7 -3
View File
@@ -19,7 +19,11 @@ log() {
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ") local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
local level="$1" local level="$1"
local msg="$2" local msg="$2"
echo "time=\"$timestamp\" level=\"$level\" msg=\"$msg\" source=\"edit_secret.sh\"">&2 if [ "$level" == "error" ]; then
echo "time=\"$timestamp\" level=\"$level\" msg=\"$msg\" source=\"edit_secret.sh\"">&2
else
echo "time=\"$timestamp\" level=\"$level\" msg=\"$msg\" source=\"edit_secret.sh\"">&1
fi
} }
# Secret file check # Secret file check
@@ -58,9 +62,9 @@ cleanup() {
trap cleanup EXIT trap cleanup EXIT
# Get GPG password from prompt # Get GPG password from prompt
echo -n "Enter GPG passphrase: " >&2 echo -n "Enter GPG passphrase: " >&1
read -s GPG_PASSPHRASE read -s GPG_PASSPHRASE
echo "" >&2 echo "" >&1
# Decrypt age-key on the tmpfs (memory) # Decrypt age-key on the tmpfs (memory)
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \ echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
+8 -32
View File
@@ -117,10 +117,9 @@ postgresql:
gitea: ENC[AES256_GCM,data:l+pBCzyQa3000SE9z1R4htD0V0ONsBtKy92dfgsVYsZ3XlEyVJDIBOsugwM=,iv:5t/oHW1vFAmV/s2Ze/cV9Vuqo96Qu6QvZeRbio7VX2s=,tag:4zeQaXiXIzBpy+tXsxmN7Q==,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] 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] 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]
affine: ENC[AES256_GCM,data:XPXrcszsV06YqCJZ7CDqc4rCwqqNlbtLCFYfLAQ8jamLtft8L2UVrMA4WZo=,iv:vrWdBeckxB9tmEE628j4jhU+hSpE6TXYMGt0hh1Cg84=,tag:hlWwWUGht8NqWTZREMsa1Q==,type:str] affine: ENC[AES256_GCM,data:XPXrcszsV06YqCJZ7CDqc4rCwqqNlbtLCFYfLAQ8jamLtft8L2UVrMA4WZo=,iv:vrWdBeckxB9tmEE628j4jhU+hSpE6TXYMGt0hh1Cg84=,tag:hlWwWUGht8NqWTZREMsa1Q==,type:str]
nextcloud: ENC[AES256_GCM,data:ROsximNuWYMTZktmLJPx7W1Qol/uT+APgwoCtFO/6ZYYc3KxKvlk344eqEc=,iv:4d+MrfIHjJKAcwhvZ3g4go66uZcieuL7lngKErJd+fg=,tag:QbWOtxeCbiu62GyrE2atXg==,type:str] nextcloud: ENC[AES256_GCM,data:ROsximNuWYMTZktmLJPx7W1Qol/uT+APgwoCtFO/6ZYYc3KxKvlk344eqEc=,iv:4d+MrfIHjJKAcwhvZ3g4go66uZcieuL7lngKErJd+fg=,tag:QbWOtxeCbiu62GyrE2atXg==,type:str]
ezbookkeeping: ENC[AES256_GCM,data:CYYQ5DVr8Na46QduvUNF6d0XBVSXTml34q3/PhIYIvUNviOVgCjqXA4wN7g=,iv:qRljohJ+wI50XxSgMElKp65HyV3mKRTqDGjw9C1S0d0=,tag:PClp7PRmC0+PV0SzZpJqqQ==,type:str] sure: ENC[AES256_GCM,data:FULJ2gjJ2gZC3s324itW+CjGRBHIP9RnOqw5TT1UaiUhb7UHAPm1na+LsZk=,iv:c0GnVZkxprJUzPPq3TCQaZvAes9QQuvDXqgVLLaiQIg=,tag:uDxy/Lkd2hNK4AWwMNMslw==,type:str]
#ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment] #ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment]
# #
# #
@@ -211,14 +210,6 @@ immich:
#ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment] #ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment]
# #
# #
#ENC[AES256_GCM,data:bzMt0Ox0Za4dOhoo7S6dYCdK32JI9Q==,iv:PRTryIJk0tR545XY0LoHwklvsJp5+A5bEljNmzUvRhY=,tag:EVsjRUGMOadaNbMu0Xr4XA==,type:comment]
actualbudget:
oidc:
secret: ENC[AES256_GCM,data:TE2umZ9Vvr7cSfA2+TAfRadIWZN3hyOKQ6U9NqJFm5e9iiw1avI+QlnYcKI=,iv:rUWoclBRqh0tsGnMq29395Fn2NP7AXnSCd0s+S8jQ6I=,tag:qPX/TcdIo6BJeex7wmi02Q==,type:str]
hash: ENC[AES256_GCM,data:UjhNkGj+sxbnmPUx1V5kVYwZnzsB0aEvN8YV29lcvMbSnf9xpQWwD5C93Zu8SYrnS/p88qZpGBgAjr9Pcly3y0H1YMRt9zzbHZU3Uo0DPDrSWRQdeB/8LkcM/cwMAs8arS6PO03ECNnN5Z6aTmFdFnLjUkvUuSWMFscItAzMzhWCpeY=,iv:B06LI7Cq3NN8haOLfN3gWIpUFnvdUlq6D2XmARojDpk=,tag:MflE8qcY5j/aAA7xfPCqng==,type:str]
#ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment]
#
#
#ENC[AES256_GCM,data:McPUAbIUvtC1gdPaxTgAxAMCMWcLfg==,iv:Tp6idRf7he3sYzo8LW596C905JAaoTIhIoDUzSyRT0k=,tag:4mZQ0Swu1X9uuwjsRNhr2A==,type:comment] #ENC[AES256_GCM,data:McPUAbIUvtC1gdPaxTgAxAMCMWcLfg==,iv:Tp6idRf7he3sYzo8LW596C905JAaoTIhIoDUzSyRT0k=,tag:4mZQ0Swu1X9uuwjsRNhr2A==,type:comment]
paperless: paperless:
session_secret: ENC[AES256_GCM,data:siwCs2noeVpg9DCEZybnmo/oz11BdrHSTnHciMOu/6g=,iv:XVjhu10TIujIdUopN9+TVVqRade9EvItDWxym6YXnZs=,tag:TxLYm+4Bo7IMaTQBtMg9pQ==,type:str] session_secret: ENC[AES256_GCM,data:siwCs2noeVpg9DCEZybnmo/oz11BdrHSTnHciMOu/6g=,iv:XVjhu10TIujIdUopN9+TVVqRade9EvItDWxym6YXnZs=,tag:TxLYm+4Bo7IMaTQBtMg9pQ==,type:str]
@@ -230,22 +221,6 @@ paperless:
#ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment] #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]
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:EsRGZP7snPchEAMoQN5PoQpiOA==,iv:A/8POGq3pIw7aX5S2vyKtI2vPqH0FT6yZnpe/vVbifw=,tag:BgUYHX2zxIL7yLS0JbI1Yg==,type:comment]
opencloud:
admin:
password: ENC[AES256_GCM,data:VKG7sNTTLHCXRGf4SAlR91+hvc7PaNrnpJX/4kItVcT9W1Hdl/yKgHHD7M8=,iv:WwWnx9KuN+i/Ugwv+HY4IGDZrLHk71hsobGFOn9kml0=,tag:SS6ihrtZjLnlAJR59lw+gw==,type:str]
#ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment]
#
#
#ENC[AES256_GCM,data:k55osvepVeB1RC5hZ4IF,iv:AlhfmWwn/DiSESWc+ULJSOLUhnrKAIfWr7MeiwV8qc8=,tag:hOgptwUcY6nVxPIhu+DYgw==,type:comment] #ENC[AES256_GCM,data:k55osvepVeB1RC5hZ4IF,iv:AlhfmWwn/DiSESWc+ULJSOLUhnrKAIfWr7MeiwV8qc8=,tag:hOgptwUcY6nVxPIhu+DYgw==,type:comment]
affine: affine:
secret_key: ENC[AES256_GCM,data:LLX78DpYnha1JWhgw0sHLzIVq/oIzvT+nB7zgli4mroGbnt7WZaXCx34zKkYRwYj/+0L4IFFVdkzKtK5DO84SgFkS2Bk2iNdCMqIx80CpyiD8IWAcyRu5d6hh82PlgyxU80T/4nbLbIn0GLubPTTeUX8GC3VxRU=,iv:DnmvbhlygSHes0jAkIm4+WXMUQLzr4R4dNa33rO67v8=,tag:+2wlh+/ekiTyShWM4XBbUw==,type:str] secret_key: ENC[AES256_GCM,data:LLX78DpYnha1JWhgw0sHLzIVq/oIzvT+nB7zgli4mroGbnt7WZaXCx34zKkYRwYj/+0L4IFFVdkzKtK5DO84SgFkS2Bk2iNdCMqIx80CpyiD8IWAcyRu5d6hh82PlgyxU80T/4nbLbIn0GLubPTTeUX8GC3VxRU=,iv:DnmvbhlygSHes0jAkIm4+WXMUQLzr4R4dNa33rO67v8=,tag:+2wlh+/ekiTyShWM4XBbUw==,type:str]
@@ -267,11 +242,12 @@ nextcloud:
#ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment] #ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment]
# #
# #
#ENC[AES256_GCM,data:tMahvC9OLW4+AGLyx68SNsOPBezApw==,iv:WHx8ruuQ33J/8XtwyhvDy2cKqE7lAWvj/r5AUhdyssU=,tag:uRwheXUxqNSIhcPqGeMNog==,type:comment] #ENC[AES256_GCM,data:Fsqc2JDp9dvfgiCjdQ==,iv:3DALKKEXaP8hzXRvxD4CgfFpOiPPsOa16OB94n8WKp8=,tag:K+FF3zGrc0YLXWK/R2L3Ow==,type:comment]
ezbookkeeping: sure:
session_secret: ENC[AES256_GCM,data:InHsz/jld8E9TwI8MWpxk9x2I7dxlIsY9R6jtDK2pBA=,iv:HY5yXEC2Dce26e9/vXTIWELvVd9ZjhcCwFD0jhz5pPw=,tag:LLSJovZ0RH3CUK+se7R4Ag==,type:str]
oidc: oidc:
secret: ENC[AES256_GCM,data:ZMIfRwXDT1ujGKoc7DGvc8/O+ciB+kajo9yOwVsMsbEjl6D8gl6I0Lbsta8=,iv:++p1TTW6gDUEvh56SjMgldrpob/VWNtiYGo6wNS8cz0=,tag:LQaW333UskiN4mtIjUAguA==,type:str] 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:XyB1N3MUzBHWHAumat7/ASy/Aja/gLKmeTriOqLnMgZ9lBE1birYtFW+R0wZ+vyx79tHKVnRxzrWsxoD5jitCmHyMVrJmJKl5c4SYMhytKfBPgrNe3twcc06U+wONmgAuVpaEQlnnyzAz42SpOHbT55GegHjYzT5hXax8eRvdM6xJSY=,iv:R4+EdQuKo2JumY3cu8KPpeFezcLhlehXBxr2wVG5wHk=,tag:hpDX1x9NCCutUsnDKEf1Sg==,type:str] hash: ENC[AES256_GCM,data:EjJ+1fP7/9wG2jG0Jv2hxMLtErqxjHBstRjru79dd5ZXhqwT7S+jpLfl9WpZU9qi20ps9YP4qe7G08p6NJNXjYhQj852GQxEORRh/9StAZsPt3p8w+ePZSVbivPQH+FpPKWYxoH0VR7y3TnL66R0tKRLh1fNTc5jRy5rU5r1bfs1jZ0=,iv:0y9FxW4QdD7qHz3bPRWlwHFpvOsvlYhVrOItB6BzaE8=,tag:Wc7MZhP3QRYmvZcjpoEWtQ==,type:str]
#ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment] #ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment]
# #
# #
@@ -304,7 +280,7 @@ sops:
UmliaFNxVTBqRkI1QWJpWGpTRWxETW8KEY/8AfU73UOzCGhny1cNnd5dCNv7bHXt UmliaFNxVTBqRkI1QWJpWGpTRWxETW8KEY/8AfU73UOzCGhny1cNnd5dCNv7bHXt
k+uyWPPi+enFkVaceSwMFrA66uaWWrwAj11sXEB7yzvGFPrnAGezjQ== k+uyWPPi+enFkVaceSwMFrA66uaWWrwAj11sXEB7yzvGFPrnAGezjQ==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2026-05-06T05:55:57Z" lastmodified: "2026-05-09T14:26:51Z"
mac: ENC[AES256_GCM,data:ZIZKCze9wNuUj3C1ghHjCgVtwU9AyZPZZ0HLfEmrnPvVjyNtTlrlmj3rwR+luHSRC6kUMKWBL1NETcs3cUupcep52Ec1cxK2gHjYFK4Ri+Ep7WJB2PXzLxtxCgurrASmW1HvEihbEEpfcLA2PP4SZ7bUrjKfIPjXxcSOOKLu7kI=,iv:5evst9V+IwsB6QXNbZfKOBrUHJzI83Z4kri+P/5Xx3M=,tag:UVwMuDLQ0e/5wX8v3+L8Gg==,type:str] mac: ENC[AES256_GCM,data:TYs08ZSS2kcO5lYuhQ/IySUSQ3DpL+ba3/uNLyszht4OttR110/W/WQLiRuu/Ql6FwtDtjq6I3iNpOhmCHSv1kMCam1l99GEIYCaPUIY+TY3Zw0j7518dFXe8p/DrKRwIVXfK5lIKLIEd+eizD50HzwXXJFmU+7YDkQ1Dx+55kw=,iv:arJKJ4wO4sdQlu3GZbtultsfM6s8vbhG93tnf2EjJDc=,tag:m95gUqvn4w85XI8qVvCZpQ==,type:str]
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
version: 3.12.1 version: 3.12.1
@@ -19,7 +19,7 @@ Volume=%h/containers/affine/config:/root/.affine/config
Volume=%h/containers/affine/ssl:/etc/ssl/affine:ro Volume=%h/containers/affine/ssl:/etc/ssl/affine:ro
# General # General
Environment="TZ=Asia/Seoul" Environment="TZ={{ timezone }}"
## OIDC callback URIs ## OIDC callback URIs
Environment="AFFINE_SERVER_HOST={{ services['affine']['domain']['public'] }}.{{ domain['public'] }}" Environment="AFFINE_SERVER_HOST={{ services['affine']['domain']['public'] }}.{{ domain['public'] }}"
Environment="AFFINE_SERVER_EXTERNAL_URL=https://{{ services['affine']['domain']['public'] }}.{{ domain['public'] }}" Environment="AFFINE_SERVER_EXTERNAL_URL=https://{{ services['affine']['domain']['public'] }}.{{ domain['public'] }}"
@@ -11,7 +11,7 @@ HostName=collabora
PublishPort={{ services['collabora']['ports']['http'] }}:9980/tcp PublishPort={{ services['collabora']['ports']['http'] }}:9980/tcp
Environment="TZ=Asia/Seoul" Environment="TZ={{ timezone }}"
Environment="aliasgroup1=https://{{ services['nextcloud']['domain']['public'] }}.{{ domain['public'] }}" Environment="aliasgroup1=https://{{ services['nextcloud']['domain']['public'] }}.{{ domain['public'] }}"
# Environment="aliasgroup2=other_server_FQDN" # Environment="aliasgroup2=other_server_FQDN"
Environment="extra_params=--o:ssl.enable=false --o:ssl.termination=true --o:server_name={{ services['collabora']['domain']['public'] }}.{{ domain['public'] }} --o:admin_console.enable=false" Environment="extra_params=--o:ssl.enable=false --o:ssl.termination=true --o:server_name={{ services['collabora']['domain']['public'] }}.{{ domain['public'] }} --o:admin_console.enable=false"
@@ -19,7 +19,7 @@ Volume=%h/data/containers/gitea:/data:rw
Volume=%h/containers/gitea/ssl:/etc/ssl/gitea:ro Volume=%h/containers/gitea/ssl:/etc/ssl/gitea:ro
# General # General
Environment="TZ=Asia/Seoul" Environment="TZ={{ timezone }}"
Environment="GITEA__server__DISABLE_SSH=true" Environment="GITEA__server__DISABLE_SSH=true"
# Database # Database
Environment="GITEA__database__DB_TYPE=postgres" Environment="GITEA__database__DB_TYPE=postgres"
@@ -21,7 +21,7 @@ PodmanArgs=--group-add keep-groups
Volume=%h/containers/immich/ml/cache:/cache:rw Volume=%h/containers/immich/ml/cache:/cache:rw
Environment="TZ=Asia/Seoul" Environment="TZ={{ timezone }}"
[Service] [Service]
Restart=always Restart=always
@@ -24,7 +24,7 @@ Volume=%h/data/containers/immich:/data:rw
Volume=%h/containers/immich/ssl:/etc/ssl/immich:ro Volume=%h/containers/immich/ssl:/etc/ssl/immich:ro
# Environment # Environment
Environment="TZ=Asia/Seoul" Environment="TZ={{ timezone }}"
# The new environment from version 2.7.0 to enable CSP # The new environment from version 2.7.0 to enable CSP
Environment="IMMICH_HELMET_FILE=true" Environment="IMMICH_HELMET_FILE=true"
@@ -14,7 +14,7 @@ PublishPort={{ services[manticore_service]['ports']['manticore'] }}:9308
Volume=%h/data/containers/manticore/{{ manticore_service }}:/var/lib/manticore:rw Volume=%h/data/containers/manticore/{{ manticore_service }}:/var/lib/manticore:rw
# General # General
Environment="TZ=Asia/Seoul" Environment="TZ={{ timezone }}"
[Service] [Service]
Restart=always Restart=always
@@ -17,7 +17,7 @@ Volume=%h/containers/nextcloud/ini/upload.ini:/usr/local/etc/php/conf.d/upload.i
Volume=%h/data/containers/nextcloud/html:/var/www/html:rw Volume=%h/data/containers/nextcloud/html:/var/www/html:rw
# General # General
Environment="TZ=Asia/Seoul" Environment="TZ={{ timezone }}"
# PostgreSQL # PostgreSQL
Environment="PGSSLMODE=verify-full" Environment="PGSSLMODE=verify-full"
Environment="PGSSLROOTCERT=/etc/ssl/nextcloud/{{ root_cert_filename }}" Environment="PGSSLROOTCERT=/etc/ssl/nextcloud/{{ root_cert_filename }}"
@@ -0,0 +1,13 @@
FROM ghcr.io/paperless-ngx/paperless-ngx:{{ version['containers']['paperless'] }}
USER root
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl ca-certificates \
&& curl -fsSL https://raw.githubusercontent.com/tesseract-ocr/tessdata_best/main/kor.traineddata \
-o /usr/share/tesseract-ocr/5/tessdata/kor.traineddata \
&& curl -fsSL https://raw.githubusercontent.com/tesseract-ocr/tessdata_best/main/eng.traineddata \
-o /usr/share/tesseract-ocr/5/tessdata/eng.traineddata \
&& rm -rf /var/lib/apt/lists/*
USER paperless
@@ -8,7 +8,7 @@ After=redis_paperless.service
Wants=redis_paperless.service Wants=redis_paperless.service
[Container] [Container]
Image=ghcr.io/paperless-ngx/paperless-ngx:{{ version['containers']['paperless'] }} Image={{ domain['internal'] }}/{{ node['name'] }}/paperless-ngx:{{ version['containers']['paperless'] }}
ContainerName=paperless ContainerName=paperless
HostName=paperless HostName=paperless
PublishPort={{ services['paperless']['ports']['http'] }}:8000/tcp PublishPort={{ services['paperless']['ports']['http'] }}:8000/tcp
@@ -20,8 +20,8 @@ Volume=%h/data/containers/paperless/consume:/usr/src/paperless/consume:rw
Volume=%h/containers/paperless/ssl:/etc/ssl/paperless:ro Volume=%h/containers/paperless/ssl:/etc/ssl/paperless:ro
# General # General
Environment="TZ=Asia/Seoul" Environment="TZ={{ timezone }}"
Environment="PAPERLESS_TIME_ZONE=Asia/Seoul" Environment="PAPERLESS_TIME_ZONE={{ timezone }}"
Environment="PAPERLESS_URL=https://{{ services['paperless']['domain']['public'] }}.{{ domain['public'] }}" Environment="PAPERLESS_URL=https://{{ services['paperless']['domain']['public'] }}.{{ domain['public'] }}"
Environment="PAPERLESS_OCR_LANGUAGE=kor+eng" Environment="PAPERLESS_OCR_LANGUAGE=kor+eng"
Environment="PAPERLESS_OCR_LANGUAGES=kor" Environment="PAPERLESS_OCR_LANGUAGES=kor"
@@ -20,7 +20,7 @@ Volume=%h/containers/redis/{{ redis_service }}/redis.conf:/usr/local/etc/redis/r
Exec=redis-server /usr/local/etc/redis/redis.conf Exec=redis-server /usr/local/etc/redis/redis.conf
Environment="TZ=Asia/Seoul" Environment="TZ={{ timezone }}"
[Service] [Service]
Restart=always Restart=always
@@ -0,0 +1,67 @@
[Quadlet]
DefaultDependencies=false
[Unit]
Description=Sure Web
After=network-online.target redis_sure.service
Wants=network-online.target redis_sure.service
[Container]
Image=ghcr.io/we-promise/sure:{{ version['containers']['sure'] }}
ContainerName=sure-web
HostName=sure-web
PublishPort={{ services['sure']['ports']['http'] }}:3000/tcp
Volume=%h/data/containers/sure/storage:/rails/storage:rw
Volume=%h/containers/sure/ssl:/etc/ssl/sure:ro
# General
Environment="TZ={{ timezone }}"
Environment="SELF_HOSTED=true"
Environment="ONBOARDING_STATE=closed"
Environment="RAILS_FORCE_SSL=false"
Environment="RAILS_ASSUME_SSL=true"
Environment="APP_DOMAIN={{ services['sure']['domain']['public'] }}.{{ domain['public'] }}"
Secret=SURE_SECRET_KEY_BASE,type=env,target=SECRET_KEY_BASE
# PostgreSQL
Environment="POSTGRES_USER=sure"
Environment="POSTGRES_DB=sure_db"
Environment="DB_HOST={{ services['postgresql']['domain'] }}.{{ domain['internal'] }}"
Environment="DB_PORT={{ services['postgresql']['ports']['tcp'] }}"
Environment="PGSSLMODE=verify-full"
Environment="PGSSLROOTCERT=/etc/ssl/sure/{{ root_cert_filename }}"
Secret=SURE_POSTGRES_PASSWORD,type=env,target=POSTGRES_PASSWORD
# Redis
Environment="REDIS_URL=redis://host.containers.internal:{{ services['sure']['ports']['redis'] }}/1"
# OIDC - Authelia
Environment="OIDC_CLIENT_ID=sure"
Environment="OIDC_ISSUER=https://{{ services['authelia']['domain'] }}.{{ domain['public'] }}"
Environment="OIDC_REDIRECT_URI=https://{{ services['sure']['domain']['public'] }}.{{ domain['public'] }}/auth/openid_connect/callback"
Secret=SURE_OIDC_CLIENT_SECRET,type=env,target=OIDC_CLIENT_SECRET
Environment="OIDC_BUTTON_LABEL=Sign in with Authelia"
Environment="AUTH_JIT_MODE=create_and_link"
# email's domain, e.g. ilnmors.internal then only user@ilnmors.internal is allowed to sign-up
Environment="ALLOWED_OIDC_DOMAINS="
# WebAuthn / Passkey
Environment="WEBAUTHN_RP_ID={{ domain['public'] }}"
Environment="WEBAUTHN_ALLOWED_ORIGINS=https://{{ services['sure']['domain']['public'] }}.{{ domain['public'] }}"
# Provider
## Currency
Environment="EXCHANGE_RATE_PROVIDER=yahoo_finance"
Environment="SECURITIES_PROVIDER=yahoo_finance"
[Service]
ExecStartPre=/usr/bin/nc -zv {{ services['postgresql']['domain'] }}.{{ domain['internal'] }} {{ services['postgresql']['ports']['tcp'] }}
Restart=always
RestartSec=10s
TimeoutStopSec=120
[Install]
WantedBy=default.target
@@ -0,0 +1,67 @@
[Quadlet]
DefaultDependencies=false
[Unit]
Description=Sure Worker
After=network-online.target redis_sure.service
Wants=network-online.target redis_sure.service
[Container]
Image=ghcr.io/we-promise/sure:{{ version['containers']['sure'] }}
ContainerName=sure-worker
HostName=sure-worker
Volume=%h/data/containers/sure/storage:/rails/storage:rw
Volume=%h/containers/sure/ssl:/etc/ssl/sure:ro
Exec=bundle exec sidekiq
# General
Environment="TZ={{ timezone }}"
Environment="SELF_HOSTED=true"
Environment="ONBOARDING_STATE=closed"
Environment="RAILS_FORCE_SSL=false"
Environment="RAILS_ASSUME_SSL=true"
Environment="APP_DOMAIN={{ services['sure']['domain']['public'] }}.{{ domain['public'] }}"
Secret=SURE_SECRET_KEY_BASE,type=env,target=SECRET_KEY_BASE
# PostgreSQL
Environment="POSTGRES_USER=sure"
Environment="POSTGRES_DB=sure_db"
Environment="DB_HOST={{ services['postgresql']['domain'] }}.{{ domain['internal'] }}"
Environment="DB_PORT={{ services['postgresql']['ports']['tcp'] }}"
Environment="PGSSLMODE=verify-full"
Environment="PGSSLROOTCERT=/etc/ssl/sure/{{ root_cert_filename }}"
Secret=SURE_POSTGRES_PASSWORD,type=env,target=POSTGRES_PASSWORD
# Redis
Environment="REDIS_URL=redis://host.containers.internal:{{ services['sure']['ports']['redis'] }}/1"
# OIDC - Authelia
Environment="OIDC_CLIENT_ID=sure"
Environment="OIDC_ISSUER=https://{{ services['authelia']['domain'] }}.{{ domain['public'] }}"
Environment="OIDC_REDIRECT_URI=https://{{ services['sure']['domain']['public'] }}.{{ domain['public'] }}/auth/openid_connect/callback"
Secret=SURE_OIDC_CLIENT_SECRET,type=env,target=OIDC_CLIENT_SECRET
Environment="OIDC_BUTTON_LABEL=Sign in with Authelia"
Environment="AUTH_JIT_MODE=create_and_link"
# email's domain, e.g. ilnmors.internal then only user@ilnmors.internal is allowed to sign-up
Environment="ALLOWED_OIDC_DOMAINS="
# WebAuthn / Passkey
Environment="WEBAUTHN_RP_ID={{ domain['public'] }}"
Environment="WEBAUTHN_ALLOWED_ORIGINS=https://{{ services['sure']['domain']['public'] }}.{{ domain['public'] }}"
# Provider
## Currency
Environment="EXCHANGE_RATE_PROVIDER=yahoo_finance"
Environment="SECURITIES_PROVIDER=yahoo_finance"
[Service]
ExecStartPre=/usr/bin/nc -zv {{ services['postgresql']['domain'] }}.{{ domain['internal'] }} {{ services['postgresql']['ports']['tcp'] }}
Restart=always
RestartSec=10s
TimeoutStopSec=120
[Install]
WantedBy=default.target
@@ -18,7 +18,7 @@ PublishPort={{ services['vaultwarden']['ports']['http'] }}:80/tcp
Volume=%h/data/containers/vaultwarden:/data:rw Volume=%h/data/containers/vaultwarden:/data:rw
Volume=%h/containers/vaultwarden/ssl:/etc/ssl/vaultwarden:ro Volume=%h/containers/vaultwarden/ssl:/etc/ssl/vaultwarden:ro
Environment="TZ=Asia/Seoul" Environment="TZ={{ timezone }}"
Environment="DOMAIN=https://{{ services['vaultwarden']['domain']['public'] }}.{{ domain['public'] }}" Environment="DOMAIN=https://{{ services['vaultwarden']['domain']['public'] }}.{{ domain['public'] }}"
Environment="SIGNUPS_ALLOWED=false" Environment="SIGNUPS_ALLOWED=false"
Secret=VW_ADMIN_TOKEN,type=env,target=ADMIN_TOKEN Secret=VW_ADMIN_TOKEN,type=env,target=ADMIN_TOKEN
@@ -22,7 +22,7 @@ Volume=%h/containers/authelia/config:/config:rw
Volume=%h/containers/authelia/certs:/etc/ssl/authelia:ro Volume=%h/containers/authelia/certs:/etc/ssl/authelia:ro
# Default # Default
Environment="TZ=Asia/Seoul" Environment="TZ={{ timezone }}"
# Enable Go template engine # Enable Go template engine
# !CAUTION! # !CAUTION!
{% raw %}# If this environment were enabled, you would have to use {{/* ... /*}} for {{ go_filter }} options. Go engine always processes its own grammar first. {% raw %}# If this environment were enabled, you would have to use {{/* ... /*}} for {{ go_filter }} options. Go engine always processes its own grammar first.
@@ -93,17 +93,6 @@ notifier:
identity_providers: identity_providers:
oidc: oidc:
hmac_secret: '' # $AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE hmac_secret: '' # $AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE
# For the app which doesn't use secret.
cors:
endpoints:
- 'authorization'
- 'token'
- 'revocation'
- 'introspection'
- 'userinfo'
allowed_origins:
- 'https://{{ services['opencloud']['domain']['public'] }}.{{ domain['public'] }}'
allowed_origins_from_client_redirect_uris: true
jwks:{% raw %} jwks:{% raw %}
- algorithm: 'RS256' - algorithm: 'RS256'
use: 'sig' use: 'sig'
@@ -184,28 +173,6 @@ identity_providers:
access_token_signed_response_alg: 'none' access_token_signed_response_alg: 'none'
userinfo_signed_response_alg: 'none' userinfo_signed_response_alg: 'none'
token_endpoint_auth_method: 'client_secret_post' token_endpoint_auth_method: 'client_secret_post'
# https://www.authelia.com/integration/openid-connect/clients/actual-budget/
- client_id: 'actual-budget'
client_name: 'Actual Budget'
client_secret: '{{ hostvars['console']['actualbudget']['oidc']['hash'] }}'
public: false
authorization_policy: 'one_factor'
require_pkce: false
pkce_challenge_method: ''
redirect_uris:
- 'https://{{ services['actualbudget']['domain']['public'] }}.{{ domain['public'] }}/openid/callback'
scopes:
- 'openid'
- 'profile'
- 'groups'
- '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'
# https://www.authelia.com/integration/openid-connect/clients/paperless/ # https://www.authelia.com/integration/openid-connect/clients/paperless/
- client_id: 'paperless' - client_id: 'paperless'
client_name: 'Paperless' client_name: 'Paperless'
@@ -228,122 +195,6 @@ identity_providers:
access_token_signed_response_alg: 'none' access_token_signed_response_alg: 'none'
userinfo_signed_response_alg: 'none' userinfo_signed_response_alg: 'none'
token_endpoint_auth_method: 'client_secret_post' 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://{{ services['vikunja']['domain']['public'] }}.{{ domain['public'] }}/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'
# OpenCloud configuration
## https://docs.opencloud.eu/docs/admin/configuration/authentication-and-user-management/external-idp/
## Web
- client_id: 'opencloud'
client_name: 'OpenCloud'
public: true
authorization_policy: 'one_factor'
require_pkce: true
pkce_challenge_method: 'S256'
redirect_uris:
- 'https://{{ services['opencloud']['domain']['public'] }}.{{ domain['public'] }}/'
- 'https://{{ services['opencloud']['domain']['public'] }}.{{ domain['public'] }}/oidc-callback.html'
- 'https://{{ services['opencloud']['domain']['public'] }}.{{ domain['public'] }}/oidc-silent-redirect.html'
scopes:
- 'openid'
- 'profile'
- 'email'
- 'groups'
response_types:
- 'code'
grant_types:
- 'authorization_code'
access_token_signed_response_alg: 'RS256'
userinfo_signed_response_alg: 'none'
token_endpoint_auth_method: 'none'
## desktop
- client_id: 'OpenCloudDesktop'
client_name: 'OpenCloud'
public: true
authorization_policy: 'one_factor'
require_pkce: true
pkce_challenge_method: 'S256'
redirect_uris:
- 'http://localhost'
- 'http://127.0.0.1'
scopes:
- 'openid'
- 'profile'
- 'email'
- 'groups'
- 'offline_access'
response_types:
- 'code'
grant_types:
- 'authorization_code'
- 'refresh_token'
access_token_signed_response_alg: 'RS256'
userinfo_signed_response_alg: 'none'
token_endpoint_auth_method: 'none'
## Android
- client_id: 'OpenCloudAndroid'
client_name: 'OpenCloud'
public: true
authorization_policy: 'one_factor'
require_pkce: true
pkce_challenge_method: 'S256'
redirect_uris:
- 'oc://android.opencloud.eu'
scopes:
- 'openid'
- 'profile'
- 'email'
- 'groups'
- 'offline_access'
response_types:
- 'code'
grant_types:
- 'authorization_code'
- 'refresh_token'
access_token_signed_response_alg: 'RS256'
userinfo_signed_response_alg: 'none'
token_endpoint_auth_method: 'none'
## IOS
- client_id: 'OpenCloudIOS'
client_name: 'OpenCloud'
public: true
authorization_policy: 'one_factor'
require_pkce: true
pkce_challenge_method: 'S256'
redirect_uris:
- 'oc://ios.opencloud.eu'
scopes:
- 'openid'
- 'profile'
- 'email'
- 'groups'
- 'offline_access'
response_types:
- 'code'
grant_types:
- 'authorization_code'
- 'refresh_token'
access_token_signed_response_alg: 'RS256'
userinfo_signed_response_alg: 'none'
token_endpoint_auth_method: 'none'
# https://docs.affine.pro/self-host-affine/administer/oauth-2-0 # https://docs.affine.pro/self-host-affine/administer/oauth-2-0
- client_id: 'affine' - client_id: 'affine'
client_name: 'Affine' client_name: 'Affine'
@@ -387,20 +238,21 @@ identity_providers:
access_token_signed_response_alg: 'none' access_token_signed_response_alg: 'none'
userinfo_signed_response_alg: 'none' userinfo_signed_response_alg: 'none'
token_endpoint_auth_method: 'client_secret_post' token_endpoint_auth_method: 'client_secret_post'
# https://www.authelia.com/integration/openid-connect/clients/ezbookkeeping/ # https://www.authelia.com/integration/openid-connect/clients/sure/
- client_id: 'ezbookkeeping' - client_id: 'sure'
client_name: 'ezBookkeeping' client_name: 'Sure'
client_secret: '{{ hostvars['console']['ezbookkeeping']['oidc']['hash'] }}' client_secret: '{{ hostvars['console']['sure']['oidc']['hash'] }}'
public: false public: false
authorization_policy: 'one_factor' authorization_policy: 'one_factor'
require_pkce: true require_pkce: true
pkce_challenge_method: 'S256' pkce_challenge_method: 'S256'
redirect_uris: redirect_uris:
- 'https://budget.ilnmors.com/oauth2/callback' - 'https://{{ services['sure']['domain']['public'] }}.{{ domain['public'] }}/auth/openid_connect/callback'
scopes: scopes:
- 'openid' - 'openid'
- 'profile'
- 'email' - 'email'
- 'profile'
- 'groups'
response_types: response_types:
- 'code' - 'code'
grant_types: grant_types:
@@ -33,7 +33,7 @@ Volume=%h/containers/caddy/data:/data:rw
Volume=/var/log/caddy:/log:rw Volume=/var/log/caddy:/log:rw
{% endif %} {% endif %}
Environment="TZ=Asia/Seoul" Environment="TZ={{ timezone }}"
Secret=CADDY_ACME_KEY,target=/run/secrets/CADDY_ACME_KEY Secret=CADDY_ACME_KEY,target=/run/secrets/CADDY_ACME_KEY
{% if node['name'] == 'auth' %} {% if node['name'] == 'auth' %}
@@ -47,30 +47,12 @@
header_up Host {http.request.header.X-Forwarded-Host} header_up Host {http.request.header.X-Forwarded-Host}
} }
} }
{{ services['actualbudget']['domain']['internal'] }}.{{ domain['internal'] }} {
import private_tls
reverse_proxy host.containers.internal:{{ services['actualbudget']['ports']['http'] }} {
header_up Host {http.request.header.X-Forwarded-Host}
}
}
{{ services['paperless']['domain']['internal'] }}.{{ domain['internal'] }} { {{ services['paperless']['domain']['internal'] }}.{{ domain['internal'] }} {
import private_tls import private_tls
reverse_proxy host.containers.internal:{{ services['paperless']['ports']['http'] }} { reverse_proxy host.containers.internal:{{ services['paperless']['ports']['http'] }} {
header_up Host {http.request.header.X-Forwarded-Host} header_up Host {http.request.header.X-Forwarded-Host}
} }
} }
{{ services['vikunja']['domain']['internal'] }}.{{ domain['internal'] }} {
import private_tls
reverse_proxy host.containers.internal:{{ services['vikunja']['ports']['http'] }} {
header_up Host {http.request.header.X-Forwarded-Host}
}
}
{{ services['opencloud']['domain']['internal'] }}.{{ domain['internal'] }} {
import private_tls
reverse_proxy host.containers.internal:{{ services['opencloud']['ports']['http'] }} {
header_up Host {http.request.header.X-Forwarded-Host}
}
}
{{ services['affine']['domain']['internal'] }}.{{ domain['internal'] }} { {{ services['affine']['domain']['internal'] }}.{{ domain['internal'] }} {
import private_tls import private_tls
reverse_proxy host.containers.internal:{{ services['affine']['ports']['http'] }} { reverse_proxy host.containers.internal:{{ services['affine']['ports']['http'] }} {
@@ -89,9 +71,9 @@
header_up Host {http.request.header.X-Forwarded-Host} header_up Host {http.request.header.X-Forwarded-Host}
} }
} }
{{ services['ezbookkeeping']['domain']['internal'] }}.{{ domain['internal'] }} { {{ services['sure']['domain']['internal'] }}.{{ domain['internal'] }} {
import private_tls import private_tls
reverse_proxy host.containers.internal:{{ services['ezbookkeeping']['ports']['http'] }} { reverse_proxy host.containers.internal:{{ services['sure']['ports']['http'] }} {
header_up Host {http.request.header.X-Forwarded-Host} header_up Host {http.request.header.X-Forwarded-Host}
} }
} }
@@ -91,15 +91,6 @@
} }
} }
} }
{{ services['actualbudget']['domain']['public'] }}.{{ domain['public'] }} {
import crowdsec_log
route {
crowdsec
reverse_proxy https://{{ services['actualbudget']['domain']['internal'] }}.{{ domain['internal'] }} {
header_up Host {http.reverse_proxy.upstream.host}
}
}
}
{{ services['paperless']['domain']['public'] }}.{{ domain['public'] }} { {{ services['paperless']['domain']['public'] }}.{{ domain['public'] }} {
import crowdsec_log import crowdsec_log
route { route {
@@ -109,24 +100,6 @@
} }
} }
} }
{{ services['vikunja']['domain']['public'] }}.{{ domain['public'] }} {
import crowdsec_log
route {
crowdsec
reverse_proxy https://{{ services['vikunja']['domain']['internal'] }}.{{ domain['internal'] }} {
header_up Host {http.reverse_proxy.upstream.host}
}
}
}
{{ services['opencloud']['domain']['public'] }}.{{ domain['public'] }} {
import crowdsec_log
route {
crowdsec
reverse_proxy https://{{ services['opencloud']['domain']['internal'] }}.{{ domain['internal'] }} {
header_up Host {http.reverse_proxy.upstream.host}
}
}
}
{{ services['affine']['domain']['public'] }}.{{ domain['public'] }} { {{ services['affine']['domain']['public'] }}.{{ domain['public'] }} {
import crowdsec_log import crowdsec_log
route { route {
@@ -154,11 +127,11 @@
} }
} }
} }
{{ services['ezbookkeeping']['domain']['public'] }}.{{ domain['public'] }} { {{ services['sure']['domain']['public'] }}.{{ domain['public'] }} {
import crowdsec_log import crowdsec_log
route { route {
crowdsec crowdsec
reverse_proxy https://{{services['ezbookkeeping']['domain']['internal'] }}.{{ domain['internal'] }} { reverse_proxy https://{{services['sure']['domain']['internal'] }}.{{ domain['internal'] }} {
header_up Host {http.reverse_proxy.upstream.host} header_up Host {http.reverse_proxy.upstream.host}
} }
} }
@@ -21,7 +21,7 @@ Volume=%h/containers/ca/config:/home/step/config:rw
Volume=%h/containers/ca/db:/home/step/db:rw Volume=%h/containers/ca/db:/home/step/db:rw
Volume=%h/containers/ca/templates:/home/step/templates:rw Volume=%h/containers/ca/templates:/home/step/templates:rw
Environment="TZ=Asia/Seoul" Environment="TZ={{ timezone }}"
# Since 0.30.0, Docker CMD no longer expands PWDPATH. # Since 0.30.0, Docker CMD no longer expands PWDPATH.
#Environment="PWDPATH=/run/secrets/STEP_CA_PASSWORD" #Environment="PWDPATH=/run/secrets/STEP_CA_PASSWORD"
@@ -24,7 +24,7 @@ Volume=%h/containers/grafana/data:/var/lib/grafana:rw
Volume=%h/containers/grafana/etc:/etc/grafana:ro Volume=%h/containers/grafana/etc:/etc/grafana:ro
Volume=%h/containers/grafana/ssl:/etc/ssl/grafana:ro Volume=%h/containers/grafana/ssl:/etc/ssl/grafana:ro
Environment="TZ=Asia/Seoul" Environment="TZ={{ timezone }}"
Environment="GF_PATHS_CONFIG=/etc/grafana/grafana.ini" Environment="GF_PATHS_CONFIG=/etc/grafana/grafana.ini"
# plugin # plugin
# Environment="GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource" # Environment="GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource"
@@ -24,7 +24,7 @@ Volume=%h/containers/ldap/data:/data:rw
Volume=%h/containers/ldap/ssl:/etc/ssl/ldap:ro Volume=%h/containers/ldap/ssl:/etc/ssl/ldap:ro
# Default # Default
Environment="TZ=Asia/Seoul" Environment="TZ={{ timezone }}"
# Domain # Domain
Environment="LLDAP_LDAP_BASE_DN={{ domain['dc'] }}" Environment="LLDAP_LDAP_BASE_DN={{ domain['dc'] }}"
@@ -19,7 +19,7 @@ Volume=%h/containers/loki/data:/loki:rw
Volume=%h/containers/loki/etc:/etc/loki:ro Volume=%h/containers/loki/etc:/etc/loki:ro
Volume=%h/containers/loki/ssl:/etc/ssl/loki:ro Volume=%h/containers/loki/ssl:/etc/ssl/loki:ro
Environment="TZ=Asia/Seoul" Environment="TZ={{ timezone }}"
Exec=--config.file=/etc/loki/loki.yaml Exec=--config.file=/etc/loki/loki.yaml
@@ -21,7 +21,7 @@ Volume=%h/containers/postgresql/ssl:/etc/ssl/postgresql:ro
Volume=%h/containers/postgresql/init:/docker-entrypoint-initdb.d/:ro Volume=%h/containers/postgresql/init:/docker-entrypoint-initdb.d/:ro
Volume=%h/containers/postgresql/backups:/backups:rw Volume=%h/containers/postgresql/backups:/backups:rw
Environment="TZ=Asia/Seoul" Environment="TZ={{ timezone }}"
# This option is only for init process, after init custom config file `pg_hba.conf` will control this option. # This option is only for init process, after init custom config file `pg_hba.conf` will control this option.
Environment="POSTGRES_HOST_AUTH_METHOD=trust" Environment="POSTGRES_HOST_AUTH_METHOD=trust"
@@ -19,7 +19,7 @@ Volume=%h/containers/prometheus/data:/prometheus:rw
Volume=%h/containers/prometheus/etc:/etc/prometheus:ro Volume=%h/containers/prometheus/etc:/etc/prometheus:ro
Volume=%h/containers/prometheus/ssl:/etc/ssl/prometheus:ro Volume=%h/containers/prometheus/ssl:/etc/ssl/prometheus:ro
Environment="TZ=Asia/Seoul" Environment="TZ={{ timezone }}"
Exec=--config.file=/etc/prometheus/prometheus.yaml \ Exec=--config.file=/etc/prometheus/prometheus.yaml \
--web.config.file=/etc/prometheus/web-config.yaml \ --web.config.file=/etc/prometheus/web-config.yaml \
@@ -0,0 +1,11 @@
server:
listen: :9793
sources:
- kind: file
name: homelab-certs
paths:
- /certs/*.crt
- /certs/*.pem
- /certs/*.cer
refreshInterval: 1m
@@ -11,11 +11,12 @@ Image=docker.io/enix/x509-certificate-exporter:{{ version['containers']['x509-ex
ContainerName=x509-exporter ContainerName=x509-exporter
HostName=X509-exporter HostName=X509-exporter
Volume=%h/containers/x509-exporter/config/config.yaml:/etc/config.yaml:ro
Volume=%h/containers/x509-exporter/certs:/certs:ro Volume=%h/containers/x509-exporter/certs:/certs:ro
PublishPort={{ services['x509-exporter']['ports']['http'] }}:9793 PublishPort={{ services['x509-exporter']['ports']['http'] }}:9793
Exec=--listen-address :9793 --watch-dir=/certs Exec=--config /etc/config.yaml
[Service] [Service]
Restart=always Restart=always
@@ -12,10 +12,10 @@ whitelist:
- "{{ hostvars['fw']['network6']['console']['wg'] }}" - "{{ hostvars['fw']['network6']['console']['wg'] }}"
{% if node['name'] == 'auth' %} {% if node['name'] == 'auth' %}
expression: expression:
# budget local-first sql scrap rule
- "evt.Meta.target_fqdn == '{{ services['actualbudget']['domain']['public'] }}.{{ domain['public'] }}' && evt.Meta.http_path contains '/data/migrations/'"
# immich thumbnail request 404 error false positive # immich thumbnail request 404 error false positive
- "evt.Meta.target_fqdn == '{{ services['immich']['domain']['public'] }}.{{ domain['public'] }}' && evt.Meta.http_path contains '/api/assets/' && evt.Meta.http_path contains '/thumbnail'" - "evt.Meta.target_fqdn == '{{ services['immich']['domain']['public'] }}.{{ domain['public'] }}' && evt.Meta.http_status == '404' && evt.Meta.http_verb == 'GET' && evt.Meta.http_path contains '/api/assets/' && evt.Meta.http_path contains '/thumbnail'"
# opencloud chunk request false positive # nextcloud thumbnail/preview request error false positive
- "evt.Meta.target_fqdn == '{{ services['opencloud']['domain']['public'] }}.{{ domain['public'] }}' && evt.Meta.http_path contains '/js/chunks/'" - "evt.Meta.target_fqdn == '{{ services['nextcloud']['domain']['public'] }}.{{ domain['public'] }}' && evt.Meta.http_status == '404' && evt.Meta.http_verb == 'GET' && evt.Meta.http_path startsWith '/index.php/core/preview?'"
# nextcloud chunks.mjs request false positive
- "evt.Meta.target_fqdn == '{{ services['nextcloud']['domain']['public'] }}.{{ domain['public'] }}' && evt.Meta.http_status in ['200', '304'] && evt.Meta.http_verb == 'GET' && evt.Meta.http_path contains 'chunk.mjs'"
{% endif %} {% endif %}
View File
+7 -7
View File
@@ -14,22 +14,22 @@
## Context ## Context
- Maintaining multi nodes requires a huge amount of resources, including hardware, electricity, even administrative efforts - Maintaining multi nodes requires a huge amount of resources, including hardware, electricity, even administrative efforts
- All units which responsible for a single role should follow the Principle of Least Privilege \(PoLP\). - All units which responsible for a single role should follow the Principle of Least Privilege (PoLP).
- All units should be interchangeable on standard to avoid vendor lock-in. - All units should be interchangeable on standard to avoid vendor lock-in.
## Consideration ## Consideration
### Hypervisor ### Hypervisor
- Proxmox Virutal Environment \(PVE\) - Proxmox Virutal Environment (PVE)
- Based on Debian. - Based on Debian.
- PVE uses `qm` command which is not a standard to implement the virtual environment. - PVE uses `qm` command which is not a standard to implement the virtual environment.
- VMware ESXi - VMware ESXi
- Based on UNIX, deveoped by VMware \(Licence is not free\) - Based on UNIX, deveoped by VMware (Licence is not free)
- Hyper-V - Hyper-V
- Based on Microsoft Windows \(Licence is not free\) - Based on Microsoft Windows (Licence is not free)
- Debian Stable - Debian Stable
- Based on standard linux \(conservative\) - Based on standard linux (conservative)
- Standard virtualization technology 'Libvirt, QEMU, KVM' - Standard virtualization technology 'Libvirt, QEMU, KVM'
### Container ### Container
@@ -37,7 +37,7 @@
- Docker - Docker
- Daemon is used to run containers - Daemon is used to run containers
- Root authority required - Root authority required
- Socket and network problem is complex \(Docker bridge\) - Socket and network problem is complex (Docker bridge)
- docker-compose is an orchestration tool - docker-compose is an orchestration tool
- Rootless Podman - Rootless Podman
- Daemonless design - Daemonless design
@@ -58,7 +58,7 @@
## Decisions ## Decisions
- Use Libvirt/KVM/QEMU on pure linux \(Debian stable\). - Use Libvirt/KVM/QEMU on pure linux (Debian stable).
- Separate all services by VM, and podman rootless containers without K3S. - Separate all services by VM, and podman rootless containers without K3S.
- Orchestration stack is not needed in single node system - Orchestration stack is not needed in single node system
- Services will be defined by Quadelt to integrate into systemd and to manage them declaratively - Services will be defined by Quadelt to integrate into systemd and to manage them declaratively
+5 -5
View File
@@ -23,15 +23,15 @@
- OPNSense/pfSense - OPNSense/pfSense
- vendor lock-in - vendor lock-in
- GUI environment \(WebGUI\) can contain vulnerability - GUI environment (WebGUI) can contain vulnerability
- It is hard to manage configurations by IaC - It is hard to manage configurations by IaC
- iptables - iptables
- Previous standard of Linux - Previous standard of Linux
- IPv4 and IPv6 configuration is separated \(no inet\) - IPv4 and IPv6 configuration is separated (no inet)
- nftables - nftables
- New standard of Linux - New standard of Linux
- English grammar friendly - English grammar friendly
- IPv4 and IPv6 configuration can be set on the same table \(inet\) - IPv4 and IPv6 configuration can be set on the same table (inet)
### Flat network structure ### Flat network structure
- LAN only - LAN only
@@ -48,8 +48,8 @@
- VLAN 20: user (DHCP allocated devices) - VLAN 20: user (DHCP allocated devices)
- wg0: VPN connections - wg0: VPN connections
- Manage the rules based on roles fundamentally, furthermore manage them based on ip and ports when it is needed - Manage the rules based on roles fundamentally, furthermore manage them based on ip and ports when it is needed
- All L3 communication which needs to pass gateway should be on control of firewall \(fw\) - All L3 communication which needs to pass gateway should be on control of firewall (fw)
- All nodes including firewall uses nftables \(modern standard\) to manage the packets based on zone concept - All nodes including firewall uses nftables (modern standard) to manage the packets based on zone concept
- IPv6 has two track strategy - IPv6 has two track strategy
- Client and server, wg nodes has static ULA IP, and use NAT66 for permanency - Client and server, wg nodes has static ULA IP, and use NAT66 for permanency
- User nodes has GUA SLAAC IP from ISP for compatibility - User nodes has GUA SLAAC IP from ISP for compatibility
+7 -7
View File
@@ -24,7 +24,7 @@
### Automate protocol ### Automate protocol
- JWK/JWT provisioner - JWK/JWT provisioner
- It is hard to manage pre-shared secret values than ACME \(Especially nsupdate\) - It is hard to manage pre-shared secret values than ACME (Especially nsupdate)
- authorized_keys - authorized_keys
- When the nodes are increased, it is hard to manage authorized_key. - When the nodes are increased, it is hard to manage authorized_key.
- SSH ca.pub allow all the certificates signed by ca key, so it is not needed to manage authroized_keys from each hosts. - SSH ca.pub allow all the certificates signed by ca key, so it is not needed to manage authroized_keys from each hosts.
@@ -39,19 +39,19 @@
## Decisions ## Decisions
- Operate private CA - Operate private CA
- Root CA \(Store on coldstorage\) - 10 years - Root CA (Store on coldstorage) - 10 years
- Intermediate CA \(Online server as Step-CA\) - 5 years - Intermediate CA (Online server as Step-CA) - 5 years
- SSH CA - No period - SSH CA - No period
- Manage certificates with two track - Manage certificates with two track
- ACME with nsupdate \(using private DNS\) for web services via Caddy - 90 days - ACME with nsupdate (using private DNS) for web services via Caddy - 90 days
- Manual issuing and managing leaf certificate for infra services for independency - 2.5 years - Manual issuing and managing leaf certificate for infra services for independency - 2.5 years
- All manual issuing leaf certificate expiry date is observed by x509-exporter on infra vm - All manual issuing leaf certificate expiry date is observed by x509-exporter on infra vm
- Manage SSH certificates - Manage SSH certificates
- *-cert.pub for host \(with -h options\) - *-cert.pub for host (with -h options)
- *-cert.pub for client \(without -h options\) - *-cert.pub for client (without -h options)
## Consequences ## Consequences
- Private PKI is operated - Private PKI is operated
- Private SSH CA is operated - Private SSH CA is operated
- All external/internal communication is encrypted as TLS re-encryption. \(E2EE\) - All external/internal communication is encrypted as TLS re-encryption. (E2EE)
+4 -4
View File
@@ -12,9 +12,9 @@
## Context ## Context
- Private authoritative DNS is required to use private reserved root domain \(.internal\) - Private authoritative DNS is required to use private reserved root domain (.internal)
- Split horizon DNS needs DNS resolver, because authoritative DNS must not send queries to other DNS. - Split horizon DNS needs DNS resolver, because authoritative DNS must not send queries to other DNS.
- Automatical issuing certificates needs private authoritative DNS which supports nsupdate \(RFC 2136\) - Automatical issuing certificates needs private authoritative DNS which supports nsupdate (RFC 2136)
## Consideration ## Consideration
@@ -22,13 +22,13 @@
- AdGuard Home - AdGuard Home
- More powerful query routing than blocky - More powerful query routing than blocky
- Web UI dependency - Web UI dependency
- Extra function which is not useful \(DHCP, etc ..\) - Extra function which is not useful (DHCP, etc ..)
- Unbound DNS - Unbound DNS
- Cache and forward zone management is powerful - Cache and forward zone management is powerful
- more complex than blocky - more complex than blocky
- cache function is not that needed in this environment - cache function is not that needed in this environment
- Internal authoritative DNS only takes charge of internal communication - Internal authoritative DNS only takes charge of internal communication
- All security function is delegated to public DNS like cloudflare \(DNSSEC, etc\) - All security function is delegated to public DNS like cloudflare (DNSSEC, etc)
## Decisions ## Decisions
+3 -3
View File
@@ -29,8 +29,8 @@
- Regex based log parsing is less structured than CrowdSec's parser/scenario model - Regex based log parsing is less structured than CrowdSec's parser/scenario model
- Crowdsec - Crowdsec
- Community based rules and sinario \(CAPI\) - Community based rules and sinario (CAPI)
- Prevention based on local machines and parsers \(LAPI\) - Prevention based on local machines and parsers (LAPI)
- Bouncers can use nftables to prevent threats - Bouncers can use nftables to prevent threats
- Parser can detect even L7 attack under TLS - Parser can detect even L7 attack under TLS
@@ -43,7 +43,7 @@
- Operate Crowdsec as IPS - Operate Crowdsec as IPS
- CrowdSec uses two API server, CAPI, LAPI. - CrowdSec uses two API server, CAPI, LAPI.
- CAPI updates malicious IPs based on community decisions - CAPI updates malicious IPs based on community decisions
- LAPI decides malicious attack based on log from its parser and scenario \(Suricata, caddy, etc\) - LAPI decides malicious attack based on log from its parser and scenario (Suricata, caddy, etc)
- When CAPI, and LAPI decides block some IP based on log parsed by parser and scenarios, bouncer block the malicious accesses. - When CAPI, and LAPI decides block some IP based on log parsed by parser and scenarios, bouncer block the malicious accesses.
- Crowdsec register blacklist on nftables or iptables. - Crowdsec register blacklist on nftables or iptables.
+3 -3
View File
@@ -20,7 +20,7 @@
- HashiCorp Vault or Infisical - HashiCorp Vault or Infisical
- Very powerful, but introduces significant compute/memory overhead. - Very powerful, but introduces significant compute/memory overhead.
- Creates a "Secret Zero" problem for a single-node homelab environment because of dependency \(DB, or etc\). - Creates a "Secret Zero" problem for a single-node homelab environment because of dependency (DB, or etc).
- It is hard to operate hardware separated key servers. - It is hard to operate hardware separated key servers.
### Systemd-credential ### Systemd-credential
@@ -37,10 +37,10 @@
## Decisions ## Decisions
- All secret data which has yaml format is encrypted by sops with age-key in `secret.yaml`. - All secret data which has yaml format is encrypted by sops with age-key in `secret.yaml`.
- age-key is encrypted by gpg and ansible vault with master key \(including upper, lower case, number, special letters) above 40 characters. - age-key is encrypted by gpg and ansible vault with master key (including upper, lower case, number, special letters) above 40 characters.
- All secret data always decrypt by `edit_secret.sh` script or ansible tasks from secrets.yaml using age-key encrypted by ansible-vault. - All secret data always decrypt by `edit_secret.sh` script or ansible tasks from secrets.yaml using age-key encrypted by ansible-vault.
- decrypted secret data is always processed on ramfs, they are never saved on disk. - decrypted secret data is always processed on ramfs, they are never saved on disk.
- Master key is never saved on disk, but only cold storage \(USB, M-DISC, operators' memory\) - Master key is never saved on disk, but only cold storage (USB, M-DISC, operators' memory)
- The secret data will be saved on each servers specific directory or podman secret. - The secret data will be saved on each servers specific directory or podman secret.
- OS: - OS:
- path: /etc/secrets - path: /etc/secrets
+5 -5
View File
@@ -17,7 +17,7 @@
## Context ## Context
- All configuration file is managed by git \(IaC\) - All configuration file is managed by git (IaC)
- All data file should be backed up by kopia - All data file should be backed up by kopia
- All backup should follow 3-2-1 backup cycle - All backup should follow 3-2-1 backup cycle
@@ -45,14 +45,14 @@
- All configuration files are managed by Git - All configuration files are managed by Git
- Configuration files are based on text - Configuration files are based on text
- It is necessary to version, history management. - It is necessary to version, history management.
- Local git -> private Gitea -> github private project \(mirrored\) - Local git -> private Gitea -> github private project (mirrored)
- This fulfills 3-2-1 backup rules - This fulfills 3-2-1 backup rules
- Data files are managed by Kopia and DSM - Data files are managed by Kopia and DSM
- Local storage - kopia -> DSM's Kopia repository server - CloudSync -> Cloud server such as OneDrive or Google Drive - Local storage - kopia -> DSM's Kopia repository server - CloudSync -> Cloud server such as OneDrive or Google Drive
- This fulfills 3-2-1 backup rules - This fulfills 3-2-1 backup rules
- Data files which needs backup - Data files which needs backup
- DB data files: dump - DB data files: dump
- DB data files are located on infra:/home/infra/containers/postgresql/backups/\{cluster,$service\}/ - DB data files are located on infra:/home/infra/containers/postgresql/backups/{cluster,$service}/
- App data files: Photos, Media, etc .. - App data files: Photos, Media, etc ..
- App data files are located on app:/home/app/data/ - App data files are located on app:/home/app/data/
- Backed up files: kopia - Backed up files: kopia
@@ -60,7 +60,7 @@
- Kopia over DSM configuration is managed by runbook with equivalent CLI commands due to vendor limitation - Kopia over DSM configuration is managed by runbook with equivalent CLI commands due to vendor limitation
- Restore will be processed manually - Restore will be processed manually
- DB data files - DB data files
- From kopia server to console:$HOMELAB_PATH/data/volume/infra/postgresql/\{cluster,data\} - From kopia server to console:$HOMELAB_PATH/data/volume/infra/postgresql/{cluster,data}
- APP data files - APP data files
- From kopia server to APP vm after initiating before deploy services - From kopia server to APP vm after initiating before deploy services
- Automatic backup does not guarantee integrity of data system, so before reset the system conduct manual backup after making sure all services are shutdown. - Automatic backup does not guarantee integrity of data system, so before reset the system conduct manual backup after making sure all services are shutdown.
@@ -74,4 +74,4 @@
## Consequences ## Consequences
- All files including configuration and data back ups will fulfill 3-2-1 \(3 Copies, 2 different media, 1 offsite\) back up rules - All files including configuration and data back ups will fulfill 3-2-1 (3 Copies, 2 different media, 1 offsite) back up rules
+1 -1
View File
@@ -11,7 +11,7 @@
## Context ## Context
- App VM needs GPU for heavy workloads like Immich \(hardware transcoding and machine learning\) - App VM needs GPU for heavy workloads like Immich (hardware transcoding and machine learning)
- App VM needs huge data storage for its own services - App VM needs huge data storage for its own services
## Considerations ## Considerations
+2 -2
View File
@@ -18,7 +18,7 @@
### Hypervisor ### Hypervisor
- As a pure hypervisor, it should only operate virtualization for VM. - As a pure hypervisor, it should only operate virtualization for VM.
- Hypervisor just provides resources and dummy hub \(br\) - Hypervisor just provides resources and dummy hub (br)
### VM ### VM
@@ -30,7 +30,7 @@
### Services ### Services
- Services should be distinguished based on their needs \(Privilege\) - Services should be distinguished based on their needs (Privilege)
- Network stack, backup stack needs special privilege for low level ACL or networks. - Network stack, backup stack needs special privilege for low level ACL or networks.
- application stack doesn't need low level privilege usually - application stack doesn't need low level privilege usually
+1 -1
View File
@@ -27,7 +27,7 @@
- Removing - Removing
- Formatting - Formatting
- Destroying - Destroying
- Certificates and CA \([ADR-003](./003-pki.md)\) - Certificates and CA ([ADR-003](./003-pki.md))
- Etc. what operator decides that is sensitive - Etc. what operator decides that is sensitive
## Consequences ## Consequences
+2 -2
View File
@@ -19,7 +19,7 @@
### Apply mTLS ### Apply mTLS
- implementing mTLS needs both client certificate and server certificate - implementing mTLS needs both client certificate and server certificate
- Managing a number of certificates makes a huge operational burden \(expiry date, revocation, etc ..\) - Managing a number of certificates makes a huge operational burden (expiry date, revocation, etc ..)
## Decisions ## Decisions
@@ -30,4 +30,4 @@
- The policy is set simple - The policy is set simple
- The overhead is increased little - The overhead is increased little
- Exclude the exceptions on operation \(For the administrator\) - Exclude the exceptions on operation (For the administrator)
@@ -13,7 +13,7 @@ PublishPort={{ services['actualbudget']['ports']['http'] }}:5006
Volume=%h/data/containers/actual-budget:/data:rw Volume=%h/data/containers/actual-budget:/data:rw
Environment="TZ=Asia/Seoul" Environment="TZ={{ timezone }}"
Environment="ACTUAL_OPENID_DISCOVERY_URL=https://{{ services['authelia']['domain'] }}.{{ domain['public'] }}/.well-known/openid-configuration" Environment="ACTUAL_OPENID_DISCOVERY_URL=https://{{ services['authelia']['domain'] }}.{{ domain['public'] }}/.well-known/openid-configuration"
Environment="ACTUAL_OPENID_CLIENT_ID=actual-budget" Environment="ACTUAL_OPENID_CLIENT_ID=actual-budget"
Environment="ACTUAL_OPENID_SERVER_HOSTNAME=https://{{ services['actualbudget']['domain']['public'] }}.{{ domain['public'] }}" Environment="ACTUAL_OPENID_SERVER_HOSTNAME=https://{{ services['actualbudget']['domain']['public'] }}.{{ domain['public'] }}"
@@ -0,0 +1,26 @@
---
identity_providers:
oidc:
clients:
# https://www.authelia.com/integration/openid-connect/clients/actual-budget/
- client_id: 'actual-budget'
client_name: 'Actual Budget'
client_secret: 'secret'
public: false
authorization_policy: 'one_factor'
require_pkce: false
pkce_challenge_method: ''
redirect_uris:
- 'https://actualbudget.example.com/openid/callback'
scopes:
- 'openid'
- 'profile'
- 'groups'
- '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'
@@ -0,0 +1,6 @@
name: crowdsecurity/whitelists
description: "Local whitelist policy"
whitelist:
expression:
# budget local-first sql scrap rule
- "evt.Meta.target_fqdn == '{{ services['actualbudget']['domain']['public'] }}.{{ domain['public'] }}' && evt.Meta.http_status in ['200', '304'] && evt.Meta.http_verb == 'GET' && evt.Meta.http_path contains '/data/migrations/'"
@@ -0,0 +1,13 @@
---
services:
actualbudget:
domain:
public: ""
internal: ""
ports:
http: ""
subuid: "101000"
version:
containers:
actualbudget: "26.3.0"
@@ -0,0 +1,5 @@
---
actualbudget:
oidc:
secret: ""
hash: ""
@@ -0,0 +1,25 @@
---
identity_providers:
oidc:
clients:
# https://www.authelia.com/integration/openid-connect/clients/ezbookkeeping/
- client_id: 'ezbookkeeping'
client_name: 'ezBookkeeping'
client_secret: 'hash'
public: false
authorization_policy: 'one_factor'
require_pkce: true
pkce_challenge_method: 'S256'
redirect_uris:
- 'https://ezbookkeeping.example.com/oauth2/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'
@@ -18,7 +18,7 @@ Volume=%h/data/containers/ezbookkeeping/data:/data:rw
Volume=%h/containers/ezbookkeeping/ssl:/etc/ssl/ezbookkeeping:ro Volume=%h/containers/ezbookkeeping/ssl:/etc/ssl/ezbookkeeping:ro
# General # General
Environment="TZ=Asia/Seoul" Environment="TZ={{ timezone }}"
Environment="EBK_SERVER_DOMAIN={{ services['ezbookkeeping']['domain']['public'] }}.{{ domain['public'] }}" Environment="EBK_SERVER_DOMAIN={{ services['ezbookkeeping']['domain']['public'] }}.{{ domain['public'] }}"
Environment="EBK_SERVER_ROOT_URL=https://{{ services['ezbookkeeping']['domain']['public'] }}.{{ domain['public'] }}/" Environment="EBK_SERVER_ROOT_URL=https://{{ services['ezbookkeeping']['domain']['public'] }}.{{ domain['public'] }}/"
Environment="EBK_LOG_MODE=console" Environment="EBK_LOG_MODE=console"
@@ -0,0 +1,13 @@
---
services:
ezbookkeeping:
domain:
public: ""
internal: ""
ports:
http: ""
subuid: "100999"
version:
containers:
ezbookkeeping: "1.4.0"
@@ -0,0 +1,8 @@
---
postgresql:
password:
ezbookkeeping: ""
ezbookkeeping:
oidc:
secret: ""
hash: ""
@@ -0,0 +1,110 @@
---
identity_providers:
oidc:
# For the app which doesn't use secret.
cors:
endpoints:
- 'authorization'
- 'token'
- 'revocation'
- 'introspection'
- 'userinfo'
allowed_origins:
- 'https://opencloud.example.com'
allowed_origins_from_client_redirect_uris: true
clients:
# OpenCloud configuration
## https://docs.opencloud.eu/docs/admin/configuration/authentication-and-user-management/external-idp/
## Web
- client_id: 'opencloud'
client_name: 'OpenCloud'
public: true
authorization_policy: 'one_factor'
require_pkce: true
pkce_challenge_method: 'S256'
redirect_uris:
- 'https://opencloud.example.com/'
- 'https://opencloud.example.com/oidc-callback.html'
- 'https://opencloud.example.com/oidc-silent-redirect.html'
scopes:
- 'openid'
- 'profile'
- 'email'
- 'groups'
response_types:
- 'code'
grant_types:
- 'authorization_code'
access_token_signed_response_alg: 'RS256'
userinfo_signed_response_alg: 'none'
token_endpoint_auth_method: 'none'
## desktop
- client_id: 'OpenCloudDesktop'
client_name: 'OpenCloud'
public: true
authorization_policy: 'one_factor'
require_pkce: true
pkce_challenge_method: 'S256'
redirect_uris:
- 'http://localhost'
- 'http://127.0.0.1'
scopes:
- 'openid'
- 'profile'
- 'email'
- 'groups'
- 'offline_access'
response_types:
- 'code'
grant_types:
- 'authorization_code'
- 'refresh_token'
access_token_signed_response_alg: 'RS256'
userinfo_signed_response_alg: 'none'
token_endpoint_auth_method: 'none'
## Android
- client_id: 'OpenCloudAndroid'
client_name: 'OpenCloud'
public: true
authorization_policy: 'one_factor'
require_pkce: true
pkce_challenge_method: 'S256'
redirect_uris:
- 'oc://android.opencloud.eu'
scopes:
- 'openid'
- 'profile'
- 'email'
- 'groups'
- 'offline_access'
response_types:
- 'code'
grant_types:
- 'authorization_code'
- 'refresh_token'
access_token_signed_response_alg: 'RS256'
userinfo_signed_response_alg: 'none'
token_endpoint_auth_method: 'none'
## IOS
- client_id: 'OpenCloudIOS'
client_name: 'OpenCloud'
public: true
authorization_policy: 'one_factor'
require_pkce: true
pkce_challenge_method: 'S256'
redirect_uris:
- 'oc://ios.opencloud.eu'
scopes:
- 'openid'
- 'profile'
- 'email'
- 'groups'
- 'offline_access'
response_types:
- 'code'
grant_types:
- 'authorization_code'
- 'refresh_token'
access_token_signed_response_alg: 'RS256'
userinfo_signed_response_alg: 'none'
token_endpoint_auth_method: 'none'
@@ -0,0 +1,6 @@
name: crowdsecurity/whitelists
description: "Local whitelist policy"
whitelist:
expression:
# opencloud chunk request false positive
- "evt.Meta.target_fqdn == '{{ services['opencloud']['domain']['public'] }}.{{ domain['public'] }}' && evt.Meta.http_status in ['200', '304'] && evt.Meta.http_verb == 'GET' && evt.Meta.http_path contains '/js/chunks/'"
@@ -0,0 +1,13 @@
---
services:
opencloud:
domain:
public: ""
internal: ""
ports:
http: ""
subuid: "100999"
version:
containers:
opencloud: "4.0.6"
@@ -15,7 +15,7 @@ Volume=%h/containers/opencloud:/etc/opencloud:rw
Volume=%h/data/containers/opencloud:/var/lib/opencloud:rw Volume=%h/data/containers/opencloud:/var/lib/opencloud:rw
# General # General
Environment="TZ=Asia/Seoul" Environment="TZ={{ timezone }}"
# Log level info # Log level info
Environment="OC_LOG_LEVEL=info" Environment="OC_LOG_LEVEL=info"
# TLS configuration # TLS configuration
@@ -13,8 +13,8 @@
## Configuration ## Configuration
- **!CAUTION!** OpenCloud application \(Android, IOS, Desktop\) doesn't support standard OIDC. Every scopes and client id is hardcoded. - **!CAUTION!** OpenCloud application (Android, IOS, Desktop) doesn't support standard OIDC. Every scopes and client id is hardcoded.
- WEBFINGER_\[DESKTOP|ANDROID|IOS\]_OIDC_CLIENT_ID, WEBFINGER_\[DESKTOP|ANDROID|IOS\]_OIDC_CLIENT_SCOPES don't work on official app. - `WEBFINGER_[DESKTOP|ANDROID|IOS]_OIDC_CLIENT_ID`, `WEBFINGER_[DESKTOP|ANDROID|IOS]_OIDC_CLIENT_SCOPES` don't work on official app.
- It is impossible to set group claim in scopes. Therefore, it is hard to control roles with token including group claim. - It is impossible to set group claim in scopes. Therefore, it is hard to control roles with token including group claim.
- When authelia doesn't work, annotate `OC_EXCLUDE_RUN_SERVICES=idp` and restart to container to use local admin. - When authelia doesn't work, annotate `OC_EXCLUDE_RUN_SERVICES=idp` and restart to container to use local admin.
- This app doesn't support regex on role_assignment mapping. - This app doesn't support regex on role_assignment mapping.
@@ -0,0 +1,3 @@
---
opencloud:
admin: ""
@@ -0,0 +1,36 @@
---
identity_providers:
oidc:
claims_policies:
# trilium expects name/email value in id token, but authelia doesn't send it basically
trilium:
id_token:
- email
- email_verified
- preferred_username
- name
clients:
# https://www.authelia.com/integration/openid-connect/clients/trillium/
# The name is trilium, not trillium
- client_id: 'trilium'
client_name: 'Trilium Notes'
client_secret: 'hash'
public: false
authorization_policy: 'one_factor'
# claims policy above
claims_policy: 'trilium'
require_pkce: false
pkce_challenge_method: ''
redirect_uris:
- 'https://trilium.example.com/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'
@@ -0,0 +1,13 @@
---
services:
trilium:
domain:
public: ""
internal: ""
ports:
http: ""
subuid: "100999"
version:
containers:
trilium: "v0.102.2"

Some files were not shown because too many files have changed in this diff Show More