- Python 94.1%
- Go Template 4.3%
- Makefile 1.2%
- Shell 0.4%
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.
|
||
|---|---|---|
| bin | ||
| examples | ||
| src/gpass | ||
| .gitignore | ||
| Makefile | ||
| pyproject.toml | ||
| README.md | ||
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
staticandstatic-private:list,check,add,apply,remove,enable,disable, HTTP-01 cert issuance viacertbot --nginx. - Pending:
containerkind (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.