Demonic deployment tool that embraces systemd
0

Configure Feed

Select the types of activity you want to include in your feed.

2 1 0

Clone this repository

https://tangled.org/nelli.pds.tomasantonelli.ar/pact https://tangled.org/did:plc:rvau25a6eb4fkocttzoadkpr
git@knot.tomasantonelli.ar:nelli.pds.tomasantonelli.ar/pact git@knot.tomasantonelli.ar:did:plc:rvau25a6eb4fkocttzoadkpr

For self-hosted knots, clone URLs may differ based on your setup.



README.md

Pact#

Deployment tool that embraces systemd.

Pact separates host bootstrap (Ansible, one-shot per host) from app deployment (rootless Podman Quadlets + bash, per App) on systemd-based VPS hosts. Deploy is idempotent and never destroys running containers, networks, or volumes. Teardown is explicit — it prompts for confirmation, checks for dependents, and never removes volumes.

Requirements#

  • Host: a systemd-as-PID-1 Linux host (Alma, Debian, Fedora, or similar)
  • Container runtime: rootless Podman with lingering enabled
  • Tools: rsync, SSH key access to the host
  • Linting (dev): nix (shfmt + shellcheck), or install them directly

Repo layout#

  • apps/ — per-App Quadlet deploy scripts
    • apps/lib/common.sh — shared deploy/teardown library
    • apps/README.md — App conventions contract
  • servers/ — per-host Ansible bootstrap and configure playbooks
  • docs/adr/ — architectural decision records
  • CONTEXT.md — ubiquitous-language glossary
  • docs/agents/ — agent operating guide and issue tracker

Getting started#

From a fresh clone the workflow is: copy the server template and bootstrap a host → for each App, copy its host skeleton and provision its .env → deploy. Every user-supplied value uses a single placeholder marker, CHANGE_ME (the server template uses YOUR_SERVER_IP), so one grep -r CHANGE_ME finds every unfilled value before you deploy.

1. Bootstrap a host#

Copy the server template and fill in your VPS IP:

cp -r servers/alma-linux-9 servers/<my-host>
$EDITOR servers/<my-host>/inventory.yml

inventory.yml has one host entry (key alma-linux-9, ansible_host: YOUR_SERVER_IP). Rename the host key to <my-host> — the SSH alias you'll pass to deploy-to.sh — and replace YOUR_SERVER_IP with your VPS IP. Then bootstrap the host:

cd servers/<my-host>
ansible-playbook bootstrap.yml
ansible-playbook configure.yml

2. Deploy an App#

For each App you want to run (e.g. mail, proxy):

  1. Copy its host skeleton and fill every CHANGE_ME placeholder. Domains in Caddyfiles, the frps dashboard password, etc. all live under hosts/<my-host>/:

    cp -r apps/<app>/hosts/example apps/<app>/hosts/<my-host>
    grep -r CHANGE_ME apps/<app>/hosts/<my-host>   # fill every hit
    

    The App's own README (apps/mail/README.md, apps/proxy/README.md) lists the exact placeholders each data file expects.

  2. Provision .env (Apps that use one — mail does; proxy doesn't):

    cp apps/<app>/.env.example apps/<app>/.env
    $EDITOR apps/<app>/.env                        # fill domain placeholders
    

    Leave any *_SECRET value as CHANGE_ME: on first deploy the sync wrapper runs ensure_env locally and replaces it with a random value. Prefer a secret store? Provision .env your way instead, e.g. skate get … > apps/<app>/.env — the wrapper syncs whatever .env exists and is agnostic to how it got its contents. .env is gitignored; never commit it.

  3. Deploy:

    cd apps/<app>
    ./deploy-to.sh <my-host>
    

    deploy-to.sh syncs config/, your hosts/<my-host>/ overlay, .env, and the App source to the server, then runs deploy.sh remotely.

3. The CHANGE_ME convention#

CHANGE_ME is the single marker for "you must fill this in". Self-check before deploying:

grep -r CHANGE_ME servers/<my-host> apps/<app>/hosts/<my-host> apps/<app>/.env

deploy.sh runs check_placeholders post-sync and rejects any deploy where a CHANGE_ME survives in .env or under config/ on the server, listing the offending files. A half-filled template fails loudly instead of silently producing a broken deploy.

Writing a new App#

To add a new App, follow the conventions in apps/README.md.

Further reading#

  • apps/README.md — App conventions contract (directory structure, service naming, ownership labels, deploy/teardown invariants, dependencies, secrets)
  • CONTEXT.md — ubiquitous language (App, Service, Shared network, Ownership label, etc.)
  • docs/adr/ — architectural decisions that ground the project

Linting#

# Format shell scripts (4-space indent, POSIX syntax)
nix run nixpkgs#shfmt -- -i 4 -ln=posix -w \
  apps/lib/common.sh apps/proxy/*.sh apps/mail/*.sh apps/mail/scripts/*.sh

# Lint shell scripts
nix run nixpkgs#shellcheck -- \
  apps/lib/common.sh apps/proxy/*.sh apps/mail/*.sh apps/mail/scripts/*.sh

License#

MIT — see LICENSE.