Goblin Passport - container management system
  • Python 94.1%
  • Go Template 4.3%
  • Makefile 1.2%
  • Shell 0.4%
Find a file
John McCardle 89b6dda366 Allocate subuid/subgid when creating container service users
useradd --system skips subid allocation, but rootless podman needs a
per-user range to remap image UIDs/GIDs during unpack. Without it, the
first `systemctl start <svc>.service` fails with:

    Error: ... insufficient UIDs or GIDs available in user namespace
    (requested 0:42 for /etc/shadow)

Hit this deploying ntfy as the first real container. Fixed by hand
with usermod --add-subuids/--add-subgids; this commit makes gpass do
the same automatically.

- users.ensure_system_user() now also calls ensure_subids() after the
  useradd, so newly-created service users are immediately usable by
  podman.
- ensure_subids() is idempotent — no-op if the user already has
  entries in both /etc/subuid and /etc/subgid — so re-applying against
  a user that lost its range (e.g. manual edit) repairs the drift.
- _next_subid_start() picks the next 65536-aligned boundary after the
  highest existing allocation in either file, so we don't collide with
  anything already registered.
- checks.py grows a "subids for <user>" line under _check_container so
  gpass check catches the drift before the next service start does.
2026-04-23 17:18:57 +00:00
bin Initial walking skeleton for Goblin Passport (gpass) 2026-04-11 03:00:41 +00:00
examples Implement static/static-private lifecycle: add/apply/remove/enable/disable 2026-04-11 03:40:00 +00:00
src/gpass Allocate subuid/subgid when creating container service users 2026-04-23 17:18:57 +00:00
.gitignore Initial walking skeleton for Goblin Passport (gpass) 2026-04-11 03:00:41 +00:00
Makefile Implement static/static-private lifecycle: add/apply/remove/enable/disable 2026-04-11 03:40:00 +00:00
pyproject.toml Initial walking skeleton for Goblin Passport (gpass) 2026-04-11 03:00:41 +00:00
README.md Implement static/static-private lifecycle: add/apply/remove/enable/disable 2026-04-11 03:40:00 +00:00

Goblin Passport (gpass)

Site lifecycle tool for goblincorps.com. Reads per-site manifests from /etc/gpass/sites.d/*.toml and wires together the nginx site, backup source, user account, podman quadlet, and webroot for each registered site.

Design

Three site kinds:

kind typical use webroot / home
container a rootless-podman service reverse-proxied by nginx /var/lib/<name>
static public static site, editable by the sftpeditors group /var/www/<name>
static-private static site you manage directly over SFTP /var/www-private/<name>

Paths and helper file locations are derived from name+kind by convention so manifests stay small. See src/gpass/paths.py for the full mapping.

Manifest format

# /etc/gpass/sites.d/ntfy.toml
kind         = "container"
domain       = "ntfy.goblincorps.com"
aliases      = []              # extra hostnames for the cert
upstream_port = 8080           # loopback port the container publishes to
# websocket  = true            # enable WebSocket proxy headers
# max_body   = "8m"            # client_max_body_size override
# /etc/gpass/sites.d/agelesslinux.toml
kind    = "static"
domain  = "agelesslinux.org"
aliases = ["www.agelesslinux.org"]
# /etc/gpass/sites.d/store-agelesslinux.toml
kind    = "static-private"
domain  = "store.agelesslinux.org"
aliases = []

Commands

gpass list              list all registered sites with a one-line status
gpass check [NAME]      detailed inventory of one site (or all)
gpass add NAME          first-time bootstrap
gpass apply NAME        idempotent re-converge (use after a restore)
gpass remove NAME       tear down
gpass enable NAME       symlink the nginx site into sites-enabled
gpass disable NAME      remove the nginx symlink (leaves everything else)

Current status:

  • Implemented for static and static-private: list, check, add, apply, remove, enable, disable, HTTP-01 cert issuance via certbot --nginx.
  • Pending: container kind (blocked on first real podman service — ntfy is up next), DNS-01 cert method (blocked on ns1.ffwf.net migration), runtime checks (service active, port listening) for containers.

Adoption of existing sites

If a server already has nginx sites in sites-available/ that gpass didn't write, gpass apply leaves them alone — it only re-renders files that carry a # gpass: managed template signature line. This lets you introduce gpass to a running server without clobbering hand-written configs. To take a site under gpass ownership, delete the existing file and re-run gpass apply <name>.

apply will, however, always convert a regular file in sites-enabled/ into a proper symlink. If that regular file has diverged from sites-available/, its content is adopted into sites-available/ first (so the running config is preserved) and then symlinked.

Install

make install          # installs to /usr/local/bin/gpass
make uninstall

No dependencies — Python ≥ 3.11, standard library only.