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:
1 parent
2aab1aec1e
commit
476a6d7cd0
85 files changed
+372
-2433
No files matched your search
@@ -1,27 +1,14 @@
|
||||
---
|
||||
# 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 existing connections. A restart would briefly drop every hosted site.
|
||||
# The config path is inside the container, not caddyfile_path on the host.
|
||||
- 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 has no live equivalent of network connect, so Caddy is recreated and every hosted site restarts.
|
||||
# Defined after the reload so the cheap reload runs first.
|
||||
- name: Restart Caddy
|
||||
ansible.builtin.systemd_service:
|
||||
name: "{{ caddy_name }}.service"
|
||||
|
||||
@@ -1,15 +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:
|
||||
path: "{{ caddyfile_path }}"
|
||||
|
||||
@@ -1,33 +1,5 @@
|
||||
---
|
||||
# Gitea 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.
|
||||
|
||||
# The data tree is created here rather than left to the image, and that is not
|
||||
# housekeeping. With keep-id:uid={{ gitea_uid }} the container process runs as
|
||||
# `git`, so /usr/bin/entrypoint is the *only* part of the image's start-up that
|
||||
# root can do — and it needs root for /etc/passwd, /etc/s6 and this tree.
|
||||
#
|
||||
# Container root is a subuid on the host, but inside its user namespace it holds
|
||||
# the usual capabilities, so `mkdir`, `chmod` and `chown` all succeed; what it
|
||||
# creates is simply left owned by that subuid. The image chowns the directories it
|
||||
# knows about back to `git` — `chown -R ${USER}:git /data/gitea` and `/data/git`
|
||||
# in /etc/s6/gitea/setup, which is where app.ini comes from — so those work either
|
||||
# way. The things it does *not* chown are what this task is for, and they are the
|
||||
# ones that would otherwise be subuid-owned and invisible to the app and to a
|
||||
# plain tar backup:
|
||||
#
|
||||
# gitea/ app.ini, gitea.db, sessions, avatars, indexers
|
||||
# gitea/conf where the entrypoint would generate app.ini
|
||||
# gitea/log Gitea's file log
|
||||
# git/ repositories, LFS, and the git account's HOME
|
||||
# git/.ssh authorized_keys — written by Gitea, read by the host's sshd
|
||||
# ssh/ only ever a *mount point* — see below
|
||||
#
|
||||
# The sshd *inside* the image is the exception; it cannot use this tree at all.
|
||||
# See {{ container_sshd_hostkeys }} below.
|
||||
# Create the tree as podman_user: anything left to the image stays subuid-owned and misses a plain tar backup.
|
||||
- name: Ensure the Gitea data directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ host_directory }}"
|
||||
@@ -48,27 +20,12 @@
|
||||
- { path: gitea/conf, mode: '0755' }
|
||||
- { path: gitea/log, mode: '0755' }
|
||||
- { path: git, mode: '0755' }
|
||||
# 0700 because that is what the image's own setup script chmods it to
|
||||
# (`chmod 700 /data/git/.ssh` in /etc/s6/gitea/setup, which runs as root
|
||||
# before `su-exec git`). Set here so the mode never depends on that script
|
||||
# having run.
|
||||
# The image's setup chmods this to 0700; setting it here does not depend on that script.
|
||||
- { path: git/.ssh, mode: '0700' }
|
||||
# The in-image sshd's host keys are mounted over /data/ssh from
|
||||
# {{ container_sshd_hostkeys }}, so this directory is never written to — it
|
||||
# has to *exist*, because it is the mount point. If it does not, Podman
|
||||
# creates it during container set-up, inside the container's user namespace,
|
||||
# and it lands here owned by the subuid rather than by {{ podman_user }}:
|
||||
#
|
||||
# drwxr-xr-t 2 165536 165536 4096 ssh
|
||||
#
|
||||
# which the check at the end of this role then (correctly) fails on. Creating
|
||||
# it first is what keeps the data tree wholly {{ podman_user }}'s.
|
||||
# This is a mount point, so it must exist; letting Podman create it leaves it subuid-owned and fails the owner check.
|
||||
- { path: ssh, mode: '0755' }
|
||||
|
||||
# The image's setup creates this file if it is missing, as root — and it is the
|
||||
# one file under /data the setup never chowns back to `git`, so on a fresh host it
|
||||
# would be left subuid-owned. Writing it here, as {{ podman_user }} and 0600,
|
||||
# both avoids that and states the value the image's own environment carries.
|
||||
# The image would create this as root and never chown it back, leaving it subuid-owned.
|
||||
- name: Write the container-side .ssh/environment file
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ host_directory }}/git/.ssh/environment"
|
||||
@@ -78,16 +35,7 @@
|
||||
group: "{{ podman_user }}"
|
||||
mode: '0600'
|
||||
|
||||
# The image always starts an sshd of its own inside the container, and that sshd
|
||||
# wants host keys. This deployment does not use it — git-over-SSH arrives on the
|
||||
# host's sshd on port 22 — but it has to have somewhere to write, and the process
|
||||
# that runs it maps to a subuid that cannot touch the data directory. Keeping it
|
||||
# out here means no subuid-owned file ever lands in {{ host_directory }}, so a
|
||||
# plain tar/rsync of the data tree stays complete and error-free, and the check
|
||||
# at the end of this role stays meaningful.
|
||||
#
|
||||
# 0777 is deliberate and bounded: /home/{{ podman_user }} is 0750, so no other
|
||||
# host user can reach this path at all. The mode only matters inside the userns.
|
||||
# The in-image sshd runs as a subuid that cannot touch the data directory, and 0777 is safe because the home directory is 0750.
|
||||
- name: Ensure the scratch host key directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ container_sshd_hostkeys }}"
|
||||
@@ -107,16 +55,7 @@
|
||||
- Reload systemd user units
|
||||
- Restart Gitea
|
||||
|
||||
# Quadlet honours [Install] WantedBy= when it generates the unit, so
|
||||
# `systemctl --user enable` is neither needed nor possible on a generated unit.
|
||||
#
|
||||
# All three steps are in one block because they fail the same way and want the
|
||||
# same evidence. `systemctl start` reports success as soon as it has handed the
|
||||
# unit to systemd, so a container that dies a second later — a `User=0` missing,
|
||||
# an unwritable /data, a subuid that cannot run the image's start-up — surfaces
|
||||
# only as `activating` from `is-active`, or as a health check that never passes,
|
||||
# with the reason in the journal and nowhere else. Restart=always makes that a
|
||||
# crash loop rather than an error.
|
||||
# Quadlet honours [Install] WantedBy=, so the generated unit is never enabled.
|
||||
- name: Start and verify Gitea
|
||||
block:
|
||||
- name: Start Gitea
|
||||
@@ -126,9 +65,7 @@
|
||||
state: started
|
||||
daemon_reload: true
|
||||
|
||||
# Retried rather than checked once: podman reports `activating` for the
|
||||
# interval between systemd starting the container and its readiness
|
||||
# notification.
|
||||
# Retried because podman reports `activating` between start and readiness.
|
||||
- name: Verify Gitea is running
|
||||
ansible.builtin.command: "systemctl --user is-active {{ unit_name }}"
|
||||
register: gitea_active
|
||||
@@ -137,9 +74,7 @@
|
||||
delay: 5
|
||||
changed_when: false
|
||||
|
||||
# `is-active` says the container is running; it does not say the app can
|
||||
# serve. The image ships no HEALTHCHECK, so the unit defines one (HealthCmd=,
|
||||
# see the template) and this runs exactly that command once, synchronously.
|
||||
# The image ships no HEALTHCHECK, so the unit defines one and this runs it synchronously.
|
||||
- name: Wait for Gitea to pass its health check
|
||||
ansible.builtin.command: "podman healthcheck run {{ container_name }}"
|
||||
register: gitea_health
|
||||
@@ -148,10 +83,7 @@
|
||||
delay: 5
|
||||
changed_when: false
|
||||
rescue:
|
||||
# systemd reports "control process exited with error code" and nothing else,
|
||||
# which is useless for the usual failures. The journal is where the image's
|
||||
# own start-up output is — that is where a s6 or entrypoint permission
|
||||
# failure shows up.
|
||||
# systemd only reports "control process exited with error code", so read the journal for the real reason.
|
||||
- name: Collect Gitea diagnostics
|
||||
ansible.builtin.command: "{{ item }}"
|
||||
loop:
|
||||
@@ -170,13 +102,7 @@
|
||||
ansible.builtin.fail:
|
||||
msg: "Gitea did not start — see the diagnostics above."
|
||||
|
||||
# A wrong keep-id is the one failure here that does not look like one: Gitea
|
||||
# still serves, but from files it owns as an unmapped subuid on the host — files
|
||||
# that then disappear from a tar backup and cannot be deleted without
|
||||
# `podman unshare`. Check the whole tree, not a sample.
|
||||
#
|
||||
# Deliberately not `failed_when: false`: if `find` cannot read something in the
|
||||
# data directory, the tree is not provably clean, and that is the finding.
|
||||
# A wrong keep-id still serves, but writes subuid-owned files a tar backup misses, so check the whole tree and let find fail.
|
||||
- name: List anything in the data tree not owned by {{ podman_user }}
|
||||
ansible.builtin.command: >-
|
||||
find {{ host_directory }} ! -user {{ podman_user }}
|
||||
@@ -198,9 +124,7 @@
|
||||
path: "{{ host_directory }}/gitea/gitea.db"
|
||||
register: gitea_db
|
||||
|
||||
# Not a failure: a fresh instance legitimately has no database yet. But on a
|
||||
# migration it means the copy in readme.md has not run, and the instance that is
|
||||
# now serving is an empty installer rather than this repository's Gitea.
|
||||
# Not a failure on a fresh instance, but a migration whose copy never ran will serve an empty installer.
|
||||
- name: Report a data directory with no database in it
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
|
||||
@@ -1,44 +1,11 @@
|
||||
---
|
||||
# The host half of Gitea's git-over-SSH support.
|
||||
#
|
||||
# Port 22 belongs to the host's sshd and stays that way: clients keep using
|
||||
# git@git.tanshu.com:user/repo.git, and no remote URL changes. Four host-side
|
||||
# pieces make that work:
|
||||
#
|
||||
# 1. a `{{ git_user }}` login user with no interactive access — every key
|
||||
# Gitea accepts carries a forced command, so sshd runs the shim and
|
||||
# nothing else;
|
||||
# 2. {{ shim_path }}, the shim those forced commands invoke;
|
||||
# 3. {{ podman_exec_wrapper }} plus a one-command sudoers rule — how the shim,
|
||||
# running as `{{ git_user }}`, reaches a container owned by
|
||||
# {{ podman_user }};
|
||||
# 4. the sshd and sudoers drop-ins that tie the above together.
|
||||
#
|
||||
# What differs from the Docker-era role is the second hop. It used to be an ssh
|
||||
# into an sshd running *inside* the container; under keep-id that sshd runs as a
|
||||
# subuid and cannot read the authorized_keys Gitea writes as {{ podman_user }},
|
||||
# so the hop is a `podman exec` instead. readme.md, "How git over SSH works
|
||||
# now", is where the reasoning lives.
|
||||
#
|
||||
# Every task here is root-only (users, /usr/local/bin, /etc/ssh, /etc/sudoers),
|
||||
# so each elevates on its own. The play carries no become, because the Quadlet
|
||||
# units have to belong to the connection user.
|
||||
|
||||
# Git-over-SSH uses the host's sshd on port 22, and the shim reaches the container through a one-command sudoers rule.
|
||||
- name: Ensure the git group exists
|
||||
ansible.builtin.group:
|
||||
name: "{{ git_user }}"
|
||||
become: true
|
||||
|
||||
# The shell matters even though nobody ever gets one: sshd runs a forced command
|
||||
# through the login user's shell (`/bin/bash -c '...'`), so a nologin shell would
|
||||
# break git-over-SSH outright.
|
||||
#
|
||||
# The uid is deliberately left to the system. On monoco the host git account was
|
||||
# uid 1003 because the container's git account was 1003 and the two shared one
|
||||
# uid space; under keep-id the container's 1003 is mapped onto {{ podman_user }}
|
||||
# instead, so nothing on this host depends on this number any more — not the
|
||||
# key file (owned by name), not the sudoers rule (matched by name), not the
|
||||
# authorized_keys hook (run as {{ podman_user }}).
|
||||
# sshd runs the forced command through the login user's shell, so it must not be nologin; the uid is left to the system.
|
||||
- name: Add the git login user
|
||||
ansible.builtin.user:
|
||||
name: "{{ git_user }}"
|
||||
@@ -49,11 +16,6 @@
|
||||
create_home: true
|
||||
become: true
|
||||
|
||||
# There is deliberately no ~/.ssh for this user any more. Its old contents were
|
||||
# the shim's client key and the authorized_keys Gitea maintains; the key is
|
||||
# obsolete (nothing authenticates over ssh in this design) and the file lives in
|
||||
# the container's data tree now, where Gitea writes it. The migration notes in
|
||||
# readme.md move the file into place.
|
||||
|
||||
- name: Install the git-over-SSH passthrough shim
|
||||
ansible.builtin.template:
|
||||
@@ -82,9 +44,7 @@
|
||||
mode: '0755'
|
||||
become: true
|
||||
|
||||
# valid with `visudo -cf %s`: a syntax error in /etc/sudoers.d does not fail one
|
||||
# rule, it fails *every* sudo invocation on the host, which on a server reached
|
||||
# by ssh is a nasty way to find out.
|
||||
# A syntax error in /etc/sudoers.d breaks every sudo on the host, so validate before installing.
|
||||
- name: Let the git user reach the container as {{ podman_user }}
|
||||
ansible.builtin.template:
|
||||
src: sudoers-gitea.j2
|
||||
@@ -107,10 +67,7 @@
|
||||
register: gitea_sshd_dropin
|
||||
become: true
|
||||
|
||||
# A drop-in that does not parse is not a failed play, it is a host nobody can
|
||||
# log in to after the next reboot: sshd reads the whole directory at start-up.
|
||||
# Validate the resulting configuration before it is ever loaded, and take the
|
||||
# file away again if it does not parse.
|
||||
# A drop-in that does not parse locks everyone out on the next reboot, so validate it and remove the file if it fails.
|
||||
- name: Validate the resulting sshd configuration
|
||||
ansible.builtin.command: /usr/sbin/sshd -t
|
||||
changed_when: false
|
||||
@@ -130,8 +87,7 @@
|
||||
sshd is still running — and still restarting — on its previous
|
||||
configuration. Run `/usr/sbin/sshd -t` on the host to see why.
|
||||
|
||||
# `reload`, not `restart`: a reload re-execs sshd without dropping the connection
|
||||
# this play is running over.
|
||||
# A reload re-execs sshd without dropping the connection this play runs over.
|
||||
- name: Reload sshd
|
||||
ansible.builtin.systemd_service:
|
||||
name: "{{ sshd_service }}"
|
||||
|
||||
Reference in new issue
Block a user