Rebuild auth on Keycloak OIDC, fix rootless Ansible deploy
Replaces the single shared HTTP Basic service-account credential (which caused a production outage from a username mismatch) with per-user login: Keycloak (already running on this VM for gcnm, now also fronted on auth.friessn.de with its own "homekeeper" realm) authenticates the user once via the landing page, FastAPI verifies the OIDC id_token and mints its own signed session JWT as a cookie, and both Shiny apps forward that per-session token as a Bearer credential instead of a static shared one. Authorization is a simple ALLOWED_USERS allowlist; the old auth.users table and bcrypt seeding are gone entirely. Also carries forward the in-progress rootless Podman/Quadlet migration (gitea, homekeeper, podman roles) and fixes a pre-existing bug where each role's handlers were malformed inside tasks/main.yml instead of their own handlers/main.yml, which broke ansible-playbook entirely. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FbiCdckkTX2HyAkyi1R39d
This commit is contained in:
+24
-20
@@ -1,24 +1,28 @@
|
||||
# Homekeeper Infrastructure
|
||||
|
||||
Ansible setup for a Hetzner VM (Ubuntu 24.04) running Homekeeper via Podman.
|
||||
Ansible setup for a Hetzner VM (Ubuntu 24.04) running Homekeeper via rootless Podman.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
nginx (system) ← HTTPS, routes by path
|
||||
├── / ← static landing page (/var/www/html)
|
||||
├── /gitea/ → Gitea on :3000 (git + container registry)
|
||||
├── /api/ → homekeeper-api on :8000
|
||||
├── /beekeeper/ → homekeeper-beekeeper on :3838
|
||||
└── /listkeeper/ → homekeeper-listkeeper on :3839
|
||||
nginx (system) ← HTTPS
|
||||
├── home.friessn.de ← landing page (/var/www/html) + path routing
|
||||
│ ├── / → static landing page
|
||||
│ ├── /api/ → homekeeper-api on 127.0.0.1:8000
|
||||
│ ├── /beekeeper/ → homekeeper-beekeeper on 127.0.0.1:3838
|
||||
│ └── /listkeeper/ → homekeeper-listkeeper on 127.0.0.1:3839
|
||||
└── git.friessn.de → Gitea on 127.0.0.1:3000 (git + container registry)
|
||||
own site file, managed outside this role
|
||||
|
||||
Podman (rootful, Quadlets → systemd units)
|
||||
├── homekeeper-db postgres:17, data at /opt/homekeeper/pg_data
|
||||
Podman (rootless, user Quadlets under {{ deploy_user }} → systemd --user units)
|
||||
├── homekeeper-db postgres:17, data in named volume {{ homekeeper_db_volume }}
|
||||
├── homekeeper-api from Gitea registry, auto-update enabled
|
||||
├── homekeeper-beekeeper from Gitea registry, auto-update enabled
|
||||
└── homekeeper-listkeeper from Gitea registry, auto-update enabled
|
||||
├── homekeeper-listkeeper from Gitea registry, auto-update enabled
|
||||
└── gitea from docker.io, data in named volume {{ gitea_data_volume }}
|
||||
|
||||
gitea container from docker.io, auto-update disabled
|
||||
All containers run as {{ deploy_user }} (not root) — `loginctl enable-linger`
|
||||
keeps the user systemd instance (and its containers) running after logout/reboot.
|
||||
```
|
||||
|
||||
## First-time setup
|
||||
@@ -54,16 +58,16 @@ On your local machine, build and push the three custom images:
|
||||
|
||||
```bash
|
||||
# Login to Gitea registry
|
||||
docker login git.friessn.de -u nico
|
||||
docker login git.friessn.de -u friessn
|
||||
|
||||
# Build and push
|
||||
docker build -t git.friessn.de/nico/homekeeper-api:latest ./api
|
||||
docker build -t git.friessn.de/nico/homekeeper-beekeeper:latest ./beekeeper
|
||||
docker build -t git.friessn.de/nico/homekeeper-listkeeper:latest ./listkeeper
|
||||
docker build -t git.friessn.de/friessn/homekeeper-api:latest ./api
|
||||
docker build -t git.friessn.de/friessn/homekeeper-beekeeper:latest ./beekeeper
|
||||
docker build -t git.friessn.de/friessn/homekeeper-listkeeper:latest ./listkeeper
|
||||
|
||||
docker push git.friessn.de/nico/homekeeper-api:latest
|
||||
docker push git.friessn.de/nico/homekeeper-beekeeper:latest
|
||||
docker push git.friessn.de/nico/homekeeper-listkeeper:latest
|
||||
docker push git.friessn.de/friessn/homekeeper-api:latest
|
||||
docker push git.friessn.de/friessn/homekeeper-beekeeper:latest
|
||||
docker push git.friessn.de/friessn/homekeeper-listkeeper:latest
|
||||
```
|
||||
|
||||
### 6. Full playbook run
|
||||
@@ -76,9 +80,9 @@ ansible-playbook -i inventory/hosts.yml site.yml
|
||||
After pushing a new image to the Gitea registry, `podman auto-update` picks it up
|
||||
automatically within `autoupdate_schedule` (default: every 15 minutes).
|
||||
|
||||
For an immediate deploy:
|
||||
For an immediate deploy (containers run rootless as `deploy_user`, not root):
|
||||
```bash
|
||||
ssh root@YOUR_IP podman auto-update
|
||||
ssh nico@YOUR_IP podman auto-update
|
||||
```
|
||||
|
||||
## Secrets management
|
||||
|
||||
@@ -2,28 +2,71 @@
|
||||
# Main domain (apps + landing page)
|
||||
domain: home.friessn.de
|
||||
|
||||
# Everything runs rootless — one Linux user owns all Podman Quadlets
|
||||
# (systemd --user units under ~/.config/containers/systemd), started via
|
||||
# `loginctl enable-linger` so they survive logout/reboot without root.
|
||||
deploy_user: nico
|
||||
deploy_uid: 1000
|
||||
|
||||
# Gitea on a subdomain — cleaner than a subpath, required for container registry
|
||||
gitea_domain: git.friessn.de
|
||||
gitea_data_dir: /opt/gitea
|
||||
gitea_data_volume: gitea-data # named Podman volume, not a bind mount (avoids rootless UID mapping issues)
|
||||
gitea_http_port: 3000 # internal port, nginx proxies HTTPS → here
|
||||
gitea_admin_user: nico
|
||||
gitea_ssh_port: 2222 # published directly (nginx can't proxy SSH)
|
||||
gitea_admin_user: friessn # actual Gitea account created during the install wizard
|
||||
gitea_admin_password: CHANGE_ME # replace — set via ansible-vault in production
|
||||
gitea_admin_email: nico.friess@googlemail.com
|
||||
|
||||
# Container registry — Gitea's built-in OCI registry, same host as Gitea
|
||||
# Images: git.friessn.de/nico/homekeeper-api:latest
|
||||
# Container registry — Gitea's built-in OCI registry, same host as Gitea, over HTTPS (443)
|
||||
# Images: git.friessn.de/friessn/homekeeper-api:latest
|
||||
registry_host: "{{ gitea_domain }}"
|
||||
registry_user: "{{ gitea_admin_user }}"
|
||||
registry_token: CHANGE_ME # Gitea API token with package:write — set after first Gitea start
|
||||
registry_token: !vault |
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
33626439663535643963613436373132336333633839613965396539653834313831383039323961
|
||||
3339613430323237636532663665313331303735623137660a613032306362343435633034383965
|
||||
61383131393939393561366335376339343432636431663033363036346337633865363561303238
|
||||
3339313363656137380a663264346666346166663362656638343838643333626438646162643835
|
||||
39656564316130383766656364303035343061653164356664643433336531363334396530376432
|
||||
3162326539326134323639363661363165323632383932623434
|
||||
|
||||
# Homekeeper app
|
||||
homekeeper_data_dir: /opt/homekeeper
|
||||
homekeeper_db_volume: homekeeper-db-data # named Podman volume
|
||||
db_name: homestead
|
||||
db_user: homestead
|
||||
db_password: CHANGE_ME # replace — set via ansible-vault in production
|
||||
api_user: homestead
|
||||
api_pass: CHANGE_ME # replace
|
||||
initial_users: "nico:CHANGE_ME" # user:password pairs for API auth
|
||||
|
||||
# Auto-update timer interval (OnCalendar syntax)
|
||||
# Auth — Keycloak (already running on this VM for the unrelated gcnm app,
|
||||
# published on 127.0.0.1:8080) acts as OIDC identity provider, exposed here
|
||||
# on its own subdomain with its own realm so it's cleanly separated from
|
||||
# gcnm's realm. FastAPI verifies the Keycloak login once via the "homekeeper"
|
||||
# realm, then mints its own session JWT — see api/app/auth.py.
|
||||
# NOTE: the Keycloak container itself is not managed by this repo/role — only
|
||||
# the nginx site + the "homekeeper" realm/client (created manually in the
|
||||
# Keycloak admin console) belong to this setup.
|
||||
keycloak_domain: auth.friessn.de
|
||||
keycloak_realm: homekeeper
|
||||
# This Keycloak instance serves under a /auth path prefix (KC_HTTP_RELATIVE_PATH=/auth
|
||||
# in gcnm's docker-compose setup) — not at root.
|
||||
oidc_issuer_url: "https://{{ keycloak_domain }}/auth/realms/{{ keycloak_realm }}"
|
||||
oidc_client_id: homekeeper
|
||||
oidc_client_secret: !vault |
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
66323965333830653838356233623461323835303663353530396166653330303130616231323262
|
||||
3233626366633532643434326338313835646533343934640a333037646138393531613730616633
|
||||
64653330623639376164353033663061636436323465646662333934306337353765343739313561
|
||||
3631333636643965370a663464333430306539396164363466633636643466383461636463626631
|
||||
30343732363664356165363565393565346439646235313637346164376265326635613132663132
|
||||
6232663636656233653466393338333532383764616633653234
|
||||
session_jwt_secret: !vault |
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
64613365363266313061396438313965646462313630636631353236353032383532376631303138
|
||||
6663626664346538363464386333383136306165343163320a653763633839353637613136333464
|
||||
62373231363664633935373632346161613865623930613436306661323439656462323538623864
|
||||
6136343462376436320a666463346530653062323431626135666665373135393262633766353264
|
||||
65303164373366393635653964316664633031313536383965353330343936623532
|
||||
allowed_users: "nico" # comma-separated Keycloak usernames allowed to log in
|
||||
|
||||
# Auto-update timer interval (OnCalendar syntax) — rootless uses podman-auto-update.timer
|
||||
# in the user systemd instance, same schedule override mechanism as the system one.
|
||||
autoupdate_schedule: "*:*:0/10" # every 10 seconds
|
||||
|
||||
@@ -2,6 +2,5 @@
|
||||
all:
|
||||
hosts:
|
||||
homekeeper:
|
||||
ansible_host: YOUR_HETZNER_IP # replace with actual IP
|
||||
ansible_user: root
|
||||
# ansible_ssh_private_key_file: ~/.ssh/id_ed25519
|
||||
ansible_host: localhost
|
||||
ansible_connection: local # this repo is normally run from the target VM itself
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
- name: Reload gitea user systemd
|
||||
become_user: "{{ deploy_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ deploy_uid }}"
|
||||
systemd:
|
||||
daemon_reload: true
|
||||
scope: user
|
||||
|
||||
- name: Restart gitea
|
||||
become_user: "{{ deploy_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ deploy_uid }}"
|
||||
systemd:
|
||||
name: gitea
|
||||
state: restarted
|
||||
enabled: true
|
||||
scope: user
|
||||
@@ -1,26 +1,24 @@
|
||||
---
|
||||
- name: Create Gitea quadlet directory
|
||||
# Gitea runs rootless as {{ deploy_user }} — see group_vars/all.yml.
|
||||
- name: Enable linger for {{ deploy_user }} (user services survive logout/reboot)
|
||||
command: "loginctl enable-linger {{ deploy_user }}"
|
||||
changed_when: false
|
||||
|
||||
- name: Create user Quadlet directory
|
||||
file:
|
||||
path: /etc/containers/systemd
|
||||
path: "/home/{{ deploy_user }}/.config/containers/systemd"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
owner: "{{ deploy_user }}"
|
||||
group: "{{ deploy_user }}"
|
||||
|
||||
- name: Deploy Gitea container quadlet
|
||||
template:
|
||||
src: gitea.container.j2
|
||||
dest: /etc/containers/systemd/gitea.container
|
||||
dest: "/home/{{ deploy_user }}/.config/containers/systemd/gitea.container"
|
||||
owner: "{{ deploy_user }}"
|
||||
group: "{{ deploy_user }}"
|
||||
mode: "0644"
|
||||
notify:
|
||||
- Reload systemd
|
||||
- Reload gitea user systemd
|
||||
- Restart gitea
|
||||
|
||||
handlers:
|
||||
- name: Reload systemd
|
||||
systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Restart gitea
|
||||
systemd:
|
||||
name: gitea
|
||||
state: restarted
|
||||
enabled: true
|
||||
|
||||
@@ -6,11 +6,13 @@ After=network-online.target
|
||||
Image=docker.io/gitea/gitea:latest
|
||||
ContainerName=gitea
|
||||
PublishPort=127.0.0.1:{{ gitea_http_port }}:3000
|
||||
Volume={{ gitea_data_dir }}:/data:Z
|
||||
Environment=USER_UID=1000
|
||||
Environment=USER_GID=1000
|
||||
PublishPort={{ gitea_ssh_port }}:22
|
||||
Volume={{ gitea_data_volume }}:/data
|
||||
Environment=USER_UID={{ deploy_uid }}
|
||||
Environment=USER_GID={{ deploy_uid }}
|
||||
Environment=GITEA__server__DOMAIN={{ gitea_domain }}
|
||||
Environment=GITEA__server__ROOT_URL=https://{{ gitea_domain }}/
|
||||
Environment=GITEA__server__SSH_DOMAIN={{ gitea_domain }}
|
||||
Environment=GITEA__server__HTTP_PORT=3000
|
||||
Environment=GITEA__packages__ENABLED=true
|
||||
Environment=GITEA__packages__CHUNKED_UPLOAD_PATH=/data/tmp/package-upload
|
||||
@@ -20,4 +22,4 @@ Restart=always
|
||||
TimeoutStartSec=120
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target default.target
|
||||
WantedBy=default.target
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
- name: Reload homekeeper user systemd
|
||||
become_user: "{{ deploy_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ deploy_uid }}"
|
||||
systemd:
|
||||
daemon_reload: true
|
||||
scope: user
|
||||
|
||||
- name: Restart homekeeper
|
||||
become_user: "{{ deploy_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ deploy_uid }}"
|
||||
systemd:
|
||||
name: "{{ item }}"
|
||||
state: restarted
|
||||
enabled: true
|
||||
scope: user
|
||||
loop:
|
||||
- homekeeper-db
|
||||
- homekeeper-api
|
||||
- homekeeper-beekeeper
|
||||
- homekeeper-listkeeper
|
||||
@@ -1,21 +1,28 @@
|
||||
---
|
||||
- name: Create quadlet directory
|
||||
# Homekeeper containers run rootless as {{ deploy_user }}, same pattern as the gitea role.
|
||||
- name: Create user Quadlet directory
|
||||
file:
|
||||
path: /etc/containers/systemd
|
||||
path: "/home/{{ deploy_user }}/.config/containers/systemd"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
owner: "{{ deploy_user }}"
|
||||
group: "{{ deploy_user }}"
|
||||
|
||||
- name: Deploy homekeeper network quadlet
|
||||
template:
|
||||
src: homekeeper.network.j2
|
||||
dest: /etc/containers/systemd/homekeeper.network
|
||||
dest: "/home/{{ deploy_user }}/.config/containers/systemd/homekeeper.network"
|
||||
owner: "{{ deploy_user }}"
|
||||
group: "{{ deploy_user }}"
|
||||
mode: "0644"
|
||||
notify: Reload systemd
|
||||
notify: Reload homekeeper user systemd
|
||||
|
||||
- name: Deploy container quadlets
|
||||
template:
|
||||
src: "{{ item }}.j2"
|
||||
dest: "/etc/containers/systemd/{{ item }}"
|
||||
dest: "/home/{{ deploy_user }}/.config/containers/systemd/{{ item }}"
|
||||
owner: "{{ deploy_user }}"
|
||||
group: "{{ deploy_user }}"
|
||||
mode: "0644"
|
||||
loop:
|
||||
- homekeeper-db.container
|
||||
@@ -23,21 +30,5 @@
|
||||
- homekeeper-beekeeper.container
|
||||
- homekeeper-listkeeper.container
|
||||
notify:
|
||||
- Reload systemd
|
||||
- Reload homekeeper user systemd
|
||||
- Restart homekeeper
|
||||
|
||||
handlers:
|
||||
- name: Reload systemd
|
||||
systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Restart homekeeper
|
||||
systemd:
|
||||
name: "{{ item }}"
|
||||
state: restarted
|
||||
enabled: true
|
||||
loop:
|
||||
- homekeeper-db
|
||||
- homekeeper-api
|
||||
- homekeeper-beekeeper
|
||||
- homekeeper-listkeeper
|
||||
|
||||
@@ -13,7 +13,14 @@ Environment=DB_NAME={{ db_name }}
|
||||
Environment=DB_USER={{ db_user }}
|
||||
Environment=DB_PASSWORD={{ db_password }}
|
||||
Environment=ROOT_PATH=/api
|
||||
Environment=INITIAL_USERS={{ initial_users }}
|
||||
Environment=ENV=production
|
||||
Environment=COOKIE_SECURE=true
|
||||
Environment=PUBLIC_BASE_URL=https://{{ domain }}
|
||||
Environment=OIDC_ISSUER_URL={{ oidc_issuer_url }}
|
||||
Environment=OIDC_CLIENT_ID={{ oidc_client_id }}
|
||||
Environment=OIDC_CLIENT_SECRET={{ oidc_client_secret }}
|
||||
Environment=SESSION_JWT_SECRET={{ session_jwt_secret }}
|
||||
Environment=ALLOWED_USERS={{ allowed_users }}
|
||||
AutoUpdate=registry
|
||||
Label=io.containers.autoupdate=registry
|
||||
|
||||
@@ -22,4 +29,4 @@ Restart=always
|
||||
TimeoutStartSec=120
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target default.target
|
||||
WantedBy=default.target
|
||||
|
||||
@@ -8,8 +8,6 @@ ContainerName=homekeeper-beekeeper
|
||||
Network=homekeeper.network
|
||||
PublishPort=127.0.0.1:3838:3838
|
||||
Environment=API_URL=http://homekeeper-api:8000
|
||||
Environment=API_USER={{ api_user }}
|
||||
Environment=API_PASS={{ api_pass }}
|
||||
AutoUpdate=registry
|
||||
Label=io.containers.autoupdate=registry
|
||||
|
||||
@@ -18,4 +16,4 @@ Restart=always
|
||||
TimeoutStartSec=120
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target default.target
|
||||
WantedBy=default.target
|
||||
|
||||
@@ -6,7 +6,7 @@ After=network-online.target
|
||||
Image=docker.io/library/postgres:17
|
||||
ContainerName=homekeeper-db
|
||||
Network=homekeeper.network
|
||||
Volume={{ homekeeper_data_dir }}/pg_data:/var/lib/postgresql/data:Z
|
||||
Volume={{ homekeeper_db_volume }}:/var/lib/postgresql/data
|
||||
Environment=POSTGRES_DB={{ db_name }}
|
||||
Environment=POSTGRES_USER={{ db_user }}
|
||||
Environment=POSTGRES_PASSWORD={{ db_password }}
|
||||
@@ -20,4 +20,4 @@ Restart=always
|
||||
TimeoutStartSec=120
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target default.target
|
||||
WantedBy=default.target
|
||||
|
||||
@@ -9,8 +9,6 @@ Network=homekeeper.network
|
||||
PublishPort=127.0.0.1:3839:3839
|
||||
Environment=PORT=3839
|
||||
Environment=API_URL=http://homekeeper-api:8000
|
||||
Environment=API_USER={{ api_user }}
|
||||
Environment=API_PASS={{ api_pass }}
|
||||
AutoUpdate=registry
|
||||
Label=io.containers.autoupdate=registry
|
||||
|
||||
@@ -19,4 +17,4 @@ Restart=always
|
||||
TimeoutStartSec=120
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target default.target
|
||||
WantedBy=default.target
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: Reload nginx
|
||||
service:
|
||||
name: nginx
|
||||
state: reloaded
|
||||
@@ -14,37 +14,63 @@
|
||||
state: absent
|
||||
notify: Reload nginx
|
||||
|
||||
- name: Deploy homekeeper nginx config
|
||||
# git.friessn.de already has its own site file (deployed manually before this
|
||||
# role existed, same one-file-per-domain convention as the other sites on this
|
||||
# box) — this role only manages home.friessn.de.
|
||||
- name: Deploy home.friessn.de nginx config
|
||||
template:
|
||||
src: homekeeper.conf.j2
|
||||
dest: /etc/nginx/sites-available/homekeeper.conf
|
||||
src: home.friessn.de.conf.j2
|
||||
dest: /etc/nginx/sites-available/home.friessn.de
|
||||
mode: "0644"
|
||||
notify: Reload nginx
|
||||
|
||||
- name: Enable homekeeper nginx site
|
||||
- name: Enable home.friessn.de nginx site
|
||||
file:
|
||||
src: /etc/nginx/sites-available/homekeeper.conf
|
||||
dest: /etc/nginx/sites-enabled/homekeeper.conf
|
||||
src: /etc/nginx/sites-available/home.friessn.de
|
||||
dest: /etc/nginx/sites-enabled/home.friessn.de
|
||||
state: link
|
||||
notify: Reload nginx
|
||||
|
||||
- name: Obtain Let's Encrypt certificates
|
||||
- name: Obtain Let's Encrypt certificate for {{ domain }}
|
||||
command: >
|
||||
certbot --nginx -d {{ domain }} -d {{ gitea_domain }}
|
||||
certbot --nginx -d {{ domain }}
|
||||
--non-interactive --agree-tos -m {{ gitea_admin_email }}
|
||||
--redirect
|
||||
args:
|
||||
creates: /etc/letsencrypt/live/{{ domain }}/fullchain.pem
|
||||
notify: Reload nginx
|
||||
|
||||
# Keycloak itself runs as a plain Docker container for the unrelated gcnm
|
||||
# app (not managed by this role) — it's already published on
|
||||
# 127.0.0.1:8080, this just fronts it with TLS on its own subdomain so
|
||||
# Homekeeper (and anything else on the box) can treat it as a normal OIDC
|
||||
# provider. Requires a DNS record for {{ keycloak_domain }} pointing at this
|
||||
# VM before the certbot step below can succeed.
|
||||
- name: Deploy auth.friessn.de nginx config
|
||||
template:
|
||||
src: auth.friessn.de.conf.j2
|
||||
dest: /etc/nginx/sites-available/{{ keycloak_domain }}
|
||||
mode: "0644"
|
||||
notify: Reload nginx
|
||||
|
||||
- name: Enable auth.friessn.de nginx site
|
||||
file:
|
||||
src: /etc/nginx/sites-available/{{ keycloak_domain }}
|
||||
dest: /etc/nginx/sites-enabled/{{ keycloak_domain }}
|
||||
state: link
|
||||
notify: Reload nginx
|
||||
|
||||
- name: Obtain Let's Encrypt certificate for {{ keycloak_domain }}
|
||||
command: >
|
||||
certbot --nginx -d {{ keycloak_domain }}
|
||||
--non-interactive --agree-tos -m {{ gitea_admin_email }}
|
||||
--redirect
|
||||
args:
|
||||
creates: /etc/letsencrypt/live/{{ keycloak_domain }}/fullchain.pem
|
||||
notify: Reload nginx
|
||||
|
||||
- name: Enable nginx
|
||||
service:
|
||||
name: nginx
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
handlers:
|
||||
- name: Reload nginx
|
||||
service:
|
||||
name: nginx
|
||||
state: reloaded
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# ── Keycloak (shared identity provider — homekeeper realm lives here,
|
||||
# the gcnm app has its own separate realm): {{ keycloak_domain }} ─────────
|
||||
server {
|
||||
listen 80;
|
||||
server_name {{ keycloak_domain }};
|
||||
# certbot --nginx adds SSL redirect + listen 443 block here
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
}
|
||||
+1
-19
@@ -3,7 +3,7 @@ map $http_upgrade $connection_upgrade {
|
||||
'' close;
|
||||
}
|
||||
|
||||
# ── Main app: friessn.de ────────────────────────────────────────────────────
|
||||
# ── Main app: {{ domain }} ───────────────────────────────────────────────────
|
||||
server {
|
||||
listen 80;
|
||||
server_name {{ domain }};
|
||||
@@ -57,21 +57,3 @@ server {
|
||||
proxy_send_timeout 86400s;
|
||||
}
|
||||
}
|
||||
|
||||
# ── Gitea: git.friessn.de ───────────────────────────────────────────────────
|
||||
server {
|
||||
listen 80;
|
||||
server_name {{ gitea_domain }};
|
||||
# certbot --nginx adds SSL redirect + listen 443 block here
|
||||
|
||||
client_max_body_size 512m;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:{{ gitea_http_port }};
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
- name: Reload systemd
|
||||
systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Restart podman services
|
||||
command: systemctl daemon-reload
|
||||
|
||||
- name: Reload user systemd
|
||||
become_user: "{{ deploy_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ deploy_uid }}"
|
||||
systemd:
|
||||
daemon_reload: true
|
||||
scope: user
|
||||
@@ -4,18 +4,14 @@
|
||||
name:
|
||||
- podman
|
||||
- podman-compose # for ad-hoc use; production uses quadlets
|
||||
- slirp4netns # rootless networking / port publishing
|
||||
- uidmap # rootless subuid/subgid mapping
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Create homekeeper data directories
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ homekeeper_data_dir }}"
|
||||
- "{{ homekeeper_data_dir }}/pg_data"
|
||||
- "{{ gitea_data_dir }}"
|
||||
# All app data lives in named Podman volumes (created implicitly on first
|
||||
# `podman run`/Quadlet start), not host bind mounts — avoids rootless UID
|
||||
# mapping headaches. Nothing to pre-create here.
|
||||
|
||||
- name: Configure Gitea as additional registry
|
||||
template:
|
||||
@@ -25,35 +21,46 @@
|
||||
notify: Restart podman services
|
||||
|
||||
- name: Login to Gitea container registry
|
||||
become_user: "{{ deploy_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ deploy_uid }}"
|
||||
command: >
|
||||
podman login {{ registry_host }}:{{ registry_port }}
|
||||
podman login {{ registry_host }}
|
||||
-u {{ registry_user }} -p {{ registry_token }}
|
||||
register: login_result
|
||||
changed_when: "'Login Succeeded' in login_result.stdout"
|
||||
# Run this after Gitea is up and registry_token is set
|
||||
|
||||
- name: Enable podman-auto-update timer
|
||||
- name: Enable linger for {{ deploy_user }} (user services survive logout/reboot)
|
||||
command: "loginctl enable-linger {{ deploy_user }}"
|
||||
changed_when: false
|
||||
|
||||
- name: Enable podman-auto-update timer (user scope)
|
||||
become_user: "{{ deploy_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ deploy_uid }}"
|
||||
systemd:
|
||||
name: podman-auto-update.timer
|
||||
enabled: true
|
||||
state: started
|
||||
scope: user
|
||||
daemon_reload: true
|
||||
|
||||
- name: Override auto-update timer schedule
|
||||
- name: Override auto-update timer schedule (user scope)
|
||||
become_user: "{{ deploy_user }}"
|
||||
file:
|
||||
path: "/home/{{ deploy_user }}/.config/systemd/user/podman-auto-update.timer.d"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Deploy auto-update timer schedule override
|
||||
become_user: "{{ deploy_user }}"
|
||||
copy:
|
||||
dest: /etc/systemd/system/podman-auto-update.timer.d/override.conf
|
||||
dest: "/home/{{ deploy_user }}/.config/systemd/user/podman-auto-update.timer.d/override.conf"
|
||||
content: |
|
||||
[Timer]
|
||||
OnCalendar=
|
||||
OnCalendar={{ autoupdate_schedule }}
|
||||
AccuracySec=1s
|
||||
mode: "0644"
|
||||
notify: Reload systemd
|
||||
|
||||
handlers:
|
||||
- name: Reload systemd
|
||||
systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Restart podman services
|
||||
command: systemctl daemon-reload
|
||||
notify: Reload user systemd
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
[[registry]]
|
||||
location = "{{ registry_host }}:{{ registry_port }}"
|
||||
location = "{{ registry_host }}"
|
||||
insecure = false
|
||||
|
||||
Reference in New Issue
Block a user