Replaces the docker_container deployment on monoco with a Quadlet systemd user unit on emily, and moves state from /var/lib/gitea to ~/podman-data/gitea. Gitea is SQLite -- the live app.ini on monoco says DB_TYPE = sqlite3 with PATH = /data/gitea/gitea.db -- so there is no postgres role and the database travels inside the data tree. git-over-SSH is the part that could not simply be moved. Port 22 stays the host's sshd, so no client changes its remote, but the second hop does: the Docker-era design sshed into an sshd *inside* the container, and under keep-id that sshd runs as a subuid which cannot read the authorized_keys Gitea writes as tanshu. It is replaced by a `podman exec`, reached through a shim sshd runs as a new `git` login user, a one-command sudoers rule, and an AuthorizedKeysCommand that hands sshd the key file without the ownership check StrictModes would otherwise apply. Two things stopped it starting, and both only showed up when it was run: keep-id runs the container *process* as the uid it maps, not just the file ownership, so the whole container came up as `git` while the image's start-up is written to begin as root. /usr/bin/entrypoint rewrites /etc/passwd, /etc/s6/gitea/setup prepares /data before the run script `su-exec git`s the server, and the image's own CMD is s6-svscan, which has to create .s6-svscan/lock inside root-owned /etc/s6. The container died on the last two -- "sed: can't create temp file '/etc/passwdXXXXXX': Permission denied" and "s6-svscan: fatal: unable to open .s6-svscan/lock: Permission denied" -- and Restart=always turned that into a crash loop, which `is-active` reports as `activating` rather than as a failure. User=0 restores root for the start-up and leaves the mapping alone: the server still runs as git through su-exec, which is what satisfies mustNotRunAsRoot. (nextcloud.container carries the same pair for the same reason.) The `ssh/` directory under /data is only ever a mount point -- the in-image sshd's host keys are mounted over it from ~/podman-data/gitea-sshd -- so it is hidden inside the container and nothing ever chowns it. When Podman has to create it, it lands in the data tree owned by the subuid, and the role's own "nothing here belongs to another user" check fails on it, correctly. Creating it in the role first is what keeps the tree wholly tanshu's. Start, is-active and the health check are now one block whose rescue dumps status, journal and podman logs, because they fail the same way: `systemctl start` reports success as soon as systemd holds the unit, so a container that dies a second later surfaces only as `activating` -- which is exactly how the two failures above were reported, with the reason in the journal and nowhere the play looked. Verified on emily: the play runs to ok=27/changed=7/failed=0, the health check and the stray-owner assertion both pass, and the ssh second hop still returns the container's Gitea 28.1.0 through the wrapper. The role's comments and the readme were corrected where they explained the old failures as EPERM from the root phases: container root holds the usual capabilities inside its user namespace, so those phases succeed and leave subuid-owned files behind, which is the real reason the tree is created up front. The readme's list of "expected" chmod EPERM lines is gone, because the log is clean. The data directory was emptied of the fresh app.ini afterwards and the unit left stopped, ready for the copy from monoco.
58 lines
2.7 KiB
YAML
Executable File
58 lines
2.7 KiB
YAML
Executable File
---
|
|
# Gitea as a rootless Podman Quadlet unit on emily, behind the shared Caddy
|
|
# unit (caddy/playbook.yml), with git-over-SSH still arriving on the host's own
|
|
# port 22 so that no client has to change its remote.
|
|
#
|
|
# 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.)
|
|
#
|
|
# Unlike bitwarden/ and tandoor/ there is no postgres role here: this Gitea runs
|
|
# on SQLite, which monoco's live app.ini confirms (`DB_TYPE = sqlite3`,
|
|
# `PATH = /data/gitea/gitea.db`). The unused Docker-era Postgres variant that
|
|
# used to sit beside this file was removed for the same reason.
|
|
#
|
|
# Run with: ansible-playbook -u tanshu gitea/playbook.yml
|
|
- name: Deploy Gitea as a rootless Podman Quadlet
|
|
hosts: emily
|
|
gather_facts: true
|
|
vars_files:
|
|
- 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.
|
|
#
|
|
# network_name is a play var, so the include can see it directly.
|
|
- name: Register Gitea's network with Caddy
|
|
ansible.builtin.include_tasks: "{{ playbook_dir }}/../caddy/roles/register/tasks/main.yaml"
|
|
|
|
roles:
|
|
# The host half of git-over-SSH: the `git` login user, the passthrough shim,
|
|
# the authorized_keys hook, the sshd drop-in and the one-command sudoers
|
|
# rule. Root-only steps elevate individually — see the role for why.
|
|
- ssh
|
|
# The app: the data directory tree the image's own root phase would leave
|
|
# subuid-owned, the Quadlet unit, start + verify.
|
|
- gitea
|
|
# Appends this site's block to the shared Caddyfile and reloads Caddy.
|
|
- caddy
|