Cut the comment bloat from the Podman playbooks

Comment blocks now stay within three lines and explain only the non-obvious. Long histories, include examples, and commented-out code are gone. No task, key, or value changed; the only data change is comment text inside the generated unit files.
This commit is contained in:
tanshu committed 2026-10-18 22:10:57 +05:30
1 parent 2aab1aec1e
commit 476a6d7cd0
85 files changed
+372 -2433

No files matched your search

+2 -39
View File
@@ -1,28 +1,5 @@
---
# Wiki.js as a rootless Podman Quadlet unit, behind the shared Caddy unit
# (caddy/playbook.yml) and with its data in the shared Postgres unit
# (postgres/playbook.yml).
#
# Connects as the container user and runs everything as that user: the Quadlet
# units belong to it, so the connection's PAM session is what makes
# `systemctl --user` work without any environment juggling.
#
# gather_facts is required: podman_user and the home directory come from the
# user_id / user_dir facts.
#
# The rootless-Podman prerequisites (installing podman, enabling lingering, the
# low-port sysctl, creating the Quadlet directory) are deliberately NOT applied
# here. They are the shared task file at caddy/roles/podman/tasks/main.yaml, and
# a host that has already run caddy/playbook.yml has them:
#
# - name: Rootless Podman prerequisites
# ansible.builtin.include_tasks: "{{ playbook_dir }}/../caddy/roles/podman/tasks/main.yaml"
#
# The variables this play needs are duplicated at the top of vars/default.yml, so
# it stands on its own. (Applying the commented include above would also need
# podman_unprivileged_port_start, which lives in caddy/vars/default.yml.)
#
# Run with: ansible-playbook -u tanshu wikijs/playbook.yml
# Requires caddy and postgres to have run first.
- name: Deploy Wiki.js as a rootless Podman Quadlet
hosts: emily
gather_facts: true
@@ -30,25 +7,11 @@
- vars/default.yml
pre_tasks:
# This must happen before the app unit is started, not after: the generated
# unit references {{ network_name }}.network, and Quadlet fails *unit
# generation* (so `systemctl --user start` reports "unit not found") when a
# referenced .network file does not exist yet. The same include writes the
# Caddy-side drop-in that attaches Caddy to this network, and the Postgres
# attach below needs the network unit to exist as well.
#
# network_name is a play var, so the include can see it directly.
# Quadlet needs the network unit before it generates the app unit.
- name: Register Wiki.js's network with Caddy
ansible.builtin.include_tasks: "{{ playbook_dir }}/../caddy/roles/register/tasks/main.yaml"
roles:
# Attaches this app's network to the shared Postgres unit, which is what
# makes DB_HOST=db resolve, and owns the "Restart Postgres" handler that
# postgres/roles/attach notifies. Runs first so the database is reachable
# before the app starts.
- postgres
# The app: content and upload directories, the 0600 environment file, the
# Quadlet unit, start + verify.
- wikijs
# Appends this site's block to the shared Caddyfile and reloads Caddy.
- caddy
+4 -16
View File
@@ -1,27 +1,15 @@
---
# Caddy is its own playbook (caddy/playbook.yml) and a rootless Quadlet unit
# there. These handlers run as the connection user, so `systemctl --user` and
# `podman exec` work without any XDG_RUNTIME_DIR juggling.
# Notified by this role when the Caddyfile changes. A reload keeps existing
# connections; a restart would briefly drop every hosted site.
#
# Note the config path: /etc/caddy/Caddyfile is the mount inside the container,
# not {{ caddyfile_path }}, which is the host side of it.
# A reload keeps live connections; a restart would drop every hosted site.
# The path is inside the container, not the host {{ caddyfile_path }}.
- name: Reload Caddy configuration
ansible.builtin.command: >-
podman exec {{ caddy_name }} caddy reload --config /etc/caddy/Caddyfile
changed_when: true
listen: "Reload Caddy"
# Notified by caddy/roles/register when this service's network unit, or the
# drop-in that attaches Caddy to it, is written. Quadlet has no live equivalent
# of `docker network connect`, so the container has to be recreated — a brief
# restart of every hosted site, not just this one. See caddy/readme.md,
# "Trade-off worth knowing".
#
# Defined after the reload above so that when both are notified the cheap config
# reload happens first.
# Quadlet cannot attach a network live, so Caddy must be recreated.
# Defined after the reload so the cheap reload runs first when both are notified.
- name: Restart Caddy
ansible.builtin.systemd_service:
name: "{{ caddy_name }}.service"
-10
View File
@@ -1,14 +1,4 @@
---
# Append this site's block to the shared Caddyfile.
#
# The Caddyfile is a bind mount into the Caddy container, so Caddy sees the
# change as soon as the file is written; the handler only asks Caddy to re-read
# it. That is a reload, not a restart, so the other hosted sites are untouched.
#
# blockinfile is idempotent on its own via the marker, so there is no need to
# slurp the file and compare first — which also matters because slurp *fails*
# when the Caddyfile does not exist yet, on a host where the Caddy playbook has
# not run.
- name: "Add the {{ host }} site block to the Caddyfile"
ansible.builtin.blockinfile:
+2 -9
View File
@@ -1,13 +1,6 @@
---
# Postgres is its own playbook (postgres/playbook.yml) and a rootless Quadlet
# unit there. This handler runs as the connection user, so `systemctl --user`
# works without setting XDG_RUNTIME_DIR.
#
# Required by postgres/roles/attach, which notifies it whenever this app's
# drop-in changes: a new drop-in changes the generated unit, so Postgres must be
# recreated to join the network — Quadlet has no live equivalent of
# `podman network connect`. That is a few seconds of downtime for every attached
# app, not just this one. See postgres/readme.md, "The trade-off".
# A new drop-in changes the generated unit, so Postgres must be recreated to join the network.
# That restarts every attached app, not just this one.
- name: Restart Postgres
ansible.builtin.systemd_service:
name: "{{ postgres_unit }}"
+4 -25
View File
@@ -1,32 +1,11 @@
---
# Attach this app's network to the shared Postgres unit.
#
# Postgres knows nothing about its clients, exactly like Caddy: a service joins
# by dropping 50-hiwikijs.conf and the shared 10-network-aliases.conf next to
# postgres.container. The canonical way to do that is the shared include below —
# it writes the same bytes every service writes, asserts that hiwikijs.network
# already exists, and notifies the "Restart Postgres" handler defined in this
# role.
#
# This runs in the roles phase, which is after the register include in
# playbook.yml's pre_tasks has created hiwikijs.network.
# Runs in the roles phase, after the register include in pre_tasks created the network.
- name: Attach Wiki.js to Postgres
ansible.builtin.include_tasks: "{{ playbook_dir }}/../postgres/roles/attach/tasks/main.yaml"
# On a first attach both drop-ins are new, so Postgres has to be recreated to
# join hiwikijs_net — and the shared 10-network-aliases.conf is what creates the
# `db` alias DB_HOST asks for. That restart is a *handler*, which by default runs
# at the end of the play, after the wikijs role has already started the app. The
# app would then come up before `db` resolves, fail its own HTTP probe, and fail
# the play on a machine where nothing is actually wrong.
#
# Flushing applies the new drop-in before the app starts. On later runs the
# drop-ins are unchanged, nothing is pending, and this is a no-op.
#
# Note that flush_handlers flushes *everything* pending, which includes the
# "Restart Caddy" that the register include notified in pre_tasks — and that is
# the right thing here: the Caddy drop-in is new too, and Caddy has to be
# recreated to join the network before it can proxy to hiwikijs.
# The restart is a handler, so it would run after the wikijs role starts the app.
# Flushing applies the new drop-in before the app starts; later runs are a no-op.
# Flush also applies Caddy's pending restart, which is right: Caddy must join the network to proxy.
- name: Apply the Postgres network change before starting Wiki.js
ansible.builtin.meta: flush_handlers
+1 -6
View File
@@ -1,16 +1,11 @@
---
# All handlers run as the connection user ({{ podman_user }}), so systemctl
# --user works without setting XDG_RUNTIME_DIR.
- name: Reload systemd user units
ansible.builtin.command: systemctl --user daemon-reload
changed_when: true
listen: Reload systemd user units
# A changed environment file or unit file changes the generated unit, so Wiki.js
# is recreated. The wiki itself is in Postgres and the two data directories are
# bind mounts, so a recreate loses nothing — but it is a few seconds of downtime
# for the one site.
# A recreate keeps the wiki in Postgres and the bind mounts, but costs a few seconds of downtime.
- name: Restart Wiki.js
ansible.builtin.systemd_service:
name: "{{ unit_name }}"
+8 -44
View File
@@ -1,19 +1,6 @@
---
# Wiki.js as a rootless Podman Quadlet unit.
#
# Everything here runs as {{ podman_user }} — the connection user. No become, no
# XDG_RUNTIME_DIR: the SSH connection gave us a real PAM session, so systemd's
# user manager is reachable through the standard environment.
# /wiki/data/content is the image's declared VOLUME, and /wiki/data/uploads is
# where Wiki.js's default "Local File System" storage target writes. Both are
# empty on monoco, because the whole wiki — pages, history, users, and the 5.8 MB
# of uploaded assets in the `assetData` table — lives in Postgres (see readme.md).
#
# They are created here anyway. The database is the store, but nothing the app
# does write may end up in the container's writable layer, where no backup would
# ever find it — that is the audit's finding #1 (docs/podman-compat-audit.md
# §3.1), and the reason the old hiwikijs-backup.sh archived nothing.
# Both must be bind mounts so nothing the app writes lands in the container layer.
- name: Ensure the Wiki.js data directories exist
ansible.builtin.file:
path: "{{ item }}"
@@ -25,9 +12,7 @@
- "{{ content_dir }}"
- "{{ uploads_dir }}"
# The app's database coordinates are consumed by systemd (EnvironmentFile= in the
# Quadlet unit), not by Ansible, so this has to be written before the unit starts.
# Mode 0600: DB_PASS carries the Postgres password.
# Written before the unit starts; 0600 keeps the Postgres password from a world-readable unit.
- name: Write the environment file
ansible.builtin.template:
src: wikijs.env.j2
@@ -57,9 +42,7 @@
state: started
daemon_reload: true
rescue:
# systemd reports "control process exited with error code" and nothing else,
# which is useless for the usual failures (an unattached network, an
# unreachable database, a bad volume). Pull the real reason out.
# systemd reports only a generic exit error, so collect the real reason.
- name: Collect Wiki.js diagnostics
ansible.builtin.command: "{{ item }}"
loop:
@@ -78,26 +61,15 @@
ansible.builtin.fail:
msg: "Wiki.js did not start — see the diagnostics above."
# Quadlet honours [Install] WantedBy= when it generates the unit, so
# `systemctl --user enable` is neither needed nor possible on a generated unit.
# Quadlet honours WantedBy=, so systemctl enable is neither needed nor possible on a generated unit.
- name: Verify Wiki.js is running
ansible.builtin.command: "systemctl --user is-active {{ unit_name }}"
register: wikijs_active
changed_when: false
failed_when: wikijs_active.stdout != 'active'
# The database is the whole wiki, and it arrives over the network that
# roles/postgres attached Postgres to. Check the name the environment file
# actually asks for, plus the container name.
#
# This runs before the HTTP probe on purpose: an unresolvable database is the one
# failure that would otherwise show up as two minutes of retries and a bare curl
# error. It is also checked *before* the app is expected to answer, because the
# network is attached when the container is created, not when the app is ready.
#
# getent is verified present in this image (/usr/bin/getent, via the musl
# libc-utils package) and exits 2 with no output when the name does not resolve,
# which is what the assert below keys off.
# An unresolvable database would otherwise show up as two minutes of retries and a bare curl error.
# getent exits 2 when the name does not resolve.
- name: Confirm Wiki.js can resolve its database
ansible.builtin.command: >-
podman exec {{ container_name }} getent hosts {{ item }}
@@ -123,16 +95,8 @@
That means hiwikijs_net was never attached to the postgres unit — see
roles/postgres and postgres/readme.md, "How services attach themselves".
# `is-active` says the container is running; it does not say the app can serve.
# Wiki.js runs its own schema migrations against Postgres before it binds port
# {{ container_port }}, and `Restart=always` means even a crash-looping container
# is reported active.
#
# `podman healthcheck run` is not available here: the image declares no
# HEALTHCHECK (verified — `docker inspect hiwikijs` reports null), unlike
# Vaultwarden's. So probe HTTP directly with the image's own /usr/bin/curl; -f
# turns the 4xx/5xx Wiki.js serves while the database is unreachable into a
# failed retry instead of a success.
# The image declares no HEALTHCHECK, so probe HTTP with its own curl.
# -f turns the 4xx/5xx served while the database is unreachable into a failed retry.
- name: Wait for Wiki.js to serve HTTP
ansible.builtin.command: >-
podman exec {{ container_name }}
+12 -63
View File
@@ -1,102 +1,51 @@
---
# ---------------------------------------------------------------------------
# Rootless Podman / Quadlet
# ---------------------------------------------------------------------------
# Duplicated on purpose from caddy/vars/default.yml: a play cannot include that
# file wholesale, because it also sets `host_directory` — to the Caddy data
# directory — and every playbook defines that for itself. Keep these three in
# sync with caddy/vars/default.yml. (Its fourth, podman_unprivileged_port_start,
# belongs to the podman tasks, which this play does not run: Wiki.js publishes
# nothing host-side.)
#
# podman_user is derived from the connection: this play carries no become, so
# the Quadlet units belong to whoever Ansible connects as. Requires gather_facts
# (set in playbook.yml).
# Derived from the connection user; requires gather_facts.
podman_user: "{{ ansible_facts['user_id'] }}"
podman_data_dir: "{{ ansible_facts['user_dir'] }}/podman-data"
quadlet_dir: "{{ ansible_facts['user_dir'] }}/.config/containers/systemd"
# ---------------------------------------------------------------------------
# Wiki.js
# ---------------------------------------------------------------------------
# The service keeps the Docker-era name everywhere — container, unit, Podman
# network and Postgres database are all `hiwikijs`, and the Caddyfile upstream is
# `hiwikijs:3000`. Only the directory is `wikijs`, because that is the playbook's
# name. Renaming the container would touch the Caddyfile, the network name and
# the database for no gain.
# The service keeps its Docker-era name hiwikijs everywhere; only the directory is wikijs.
host: wiki.hinchco.in
container_name: hiwikijs
unit_name: "{{ container_name }}.service"
# Fully qualified on purpose: registry + org + repo, so rootless Podman never
# consults unqualified-search-registries. There is no TTY to answer a short-name
# prompt when the systemd unit starts.
# Fully qualified so rootless Podman never prompts for a short name; there is no TTY.
image: ghcr.io/requarks/wiki:latest
# The Quadlet file is <network_name>.network. caddy/roles/register pins the
# Podman network name to <network_name>_net (so: hiwikijs.network,
# hiwikijs_net — the name the Docker-era container used).
# The Quadlet file is <network_name>.network; register pins <network_name>_net.
network_name: "{{ container_name }}"
host_directory: "{{ podman_data_dir }}/{{ container_name }}"
# The uid the app actually runs as: the image's `USER node`, and `node` is uid
# 1000 there (verified: `docker exec hiwikijs id` → uid=1000(node) gid=1000).
# keep-id maps that uid onto podman_user, so anything written into the mounts
# below stays owned by podman_user on the host and never by a subuid.
#
# Note this is *not* root: unlike bitwarden and tandoor (uid 0), Wiki.js runs
# unprivileged, so the mapping goes through 1000. `tanshu` is uid 1001 — "the
# data is already mine" is false, which is the trap in
# docs/podman-migration.md §4.1.
# The image runs as uid 1000, and keep-id maps it onto podman_user.
# Unlike bitwarden and tandoor, Wiki.js runs unprivileged.
wikijs_uid: 1000
wikijs_gid: 1000
# The image declares VOLUME /wiki/data/content. On Docker that silently became an
# *anonymous* volume under /var/lib/docker/volumes, which is why nothing under
# /var/lib ever held the wiki and why the old backup script archived an empty
# directory (docs/podman-compat-audit.md §3.1). Binding it to a real path is the
# fix. It is empty on monoco today — see readme.md.
# The image declares this as a volume; bind it to a real path so backups can find it.
content_dir: "{{ host_directory }}/content"
# Not a declared volume, but it is where Wiki.js's default "Local File System"
# storage target writes uploads. Empty on monoco today — the wiki's 49 uploaded
# assets are rows in Postgres (`assetData`, 5.8 MB of the 16 MB database) — so
# this is insurance: if the storage target is ever switched to local files, the
# bytes land in a directory that gets backed up instead of in the container's
# writable layer, where nothing would ever find them.
# Not a declared volume, but a local storage target would write here.
uploads_dir: "{{ host_directory }}/uploads"
# DB_PASS carries the Postgres password and the Quadlet unit is world-readable in
# ~/.config, so it goes in this 0600 file instead and is read by systemd through
# EnvironmentFile=.
# The unit is world-readable, so the password goes in this 0600 file.
env_file: "{{ host_directory }}/{{ container_name }}.env"
# From the image's own config.yml (`port: 3000`, `bindIP: 0.0.0.0`). Caddy
# proxies to it over the app network; nothing is published host-side.
# The image's config.yml sets port 3000.
container_port: 3000
# The shared Postgres unit this app attaches itself to (postgres/playbook.yml).
# roles/postgres owns this app's drop-ins there and the "Restart Postgres"
# handler that applies them.
postgres_container: postgres
postgres_unit: "{{ postgres_container }}.service"
# The credentials the Docker-era playbook embedded in its .env file, kept as-is
# so the already-restored `hiwikijs` database and the app's own connection string
# agree. Postgres is authoritative for this service; nothing here is new.
# Kept as-is so the restored database and the app's connection string agree.
pg_user: postgres
pg_password: '123456'
pg_database: hiwikijs
# `db`, not `postgres`: that is literally what the Docker-era container ran with
# (it was `--link postgres:db`), and postgres/roles/attach writes the shared
# NetworkAlias=db drop-in that makes it resolve on hiwikijs_net. The container
# name resolves too, and the play checks both.
# The image expects the db alias; postgres/roles/attach writes the NetworkAlias=db drop-in.
db_host: db
# The shared Caddy unit and its Caddyfile, both owned by caddy/playbook.yml.
caddy_name: caddy
caddyfile_path: "{{ podman_data_dir }}/{{ caddy_name }}/conf/Caddyfile"