Pets
Minimalist configuration management for "pet" Debian Linux servers. Declare
intent with primitives, group primitives into a role, then check/apply
across a fleet.
🤖 experimental 🤖
This was built to learn about swamp extensions, don't use it!
Every model takes a fleet (@swamp/ssh model instance), and a hosts
selector (all, name:X, tag:Y). Convergence logic streams to each host's
python3 over SSH stdin; nothing is installed on the targets. check is
read-only; apply converges with sudo. Statuses: compliant, non_compliant,
applied (real changes made), failed.
Quickstart by example: PostgreSQL from PGDG repositories
Define a fleet:
swamp extension pull @swamp/ssh
swamp model create @swamp/ssh my-fleet
swamp model edit my-fleettransport:
kind: ssh
user: debian
identityFile: ~/.ssh/id_ed25519
hosts:
- name: build
address: build.example.com
tags: [build]Declare our APT repository (see: PGDG):
swamp model create @psftw/pets/apt-repository pgdg-repo
swamp model edit pgdg-repofleet: my-fleet
name: pgdg-repo
uris: [https://apt.postgresql.org/pub/repos/apt]
suites: [trixie-pgdg]
components: [main]
gpgKeyUrl: https://www.postgresql.org/media/keys/ACCC4CF8.asc
signedBy: /etc/apt/keyrings/pgdg.ascDeclare our package install:
swamp model create @psftw/pets/apt postgres-package
swamp model edit postgres-packagefleet: my-fleet
packages:
- postgresql-18Sequence these into a role:
swamp model create @psftw/pets/role postgres-role
swamp model edit postgres-rolefleet: my-fleet
members:
- pgdg-repo
- postgres-packageApply the role to our server:
swamp model method run postgres-role apply --input hosts=all
[INF] @psftw/pets/role·apply: [1/2] pgdg-repo: apply on all
[INF] @psftw/pets/apt-repository·apply: running on all via my-fleet (sudo)
[INF] @psftw/pets/apt-repository·apply: build: applied — write /etc/apt/sources.list.d/pgdg-repo.sources; fetch key to /etc/apt/keyrings/pgdg.asc
[INF] @psftw/pets/role·apply: [1/2] pgdg-repo: applied=1
[INF] @psftw/pets/role·apply: [2/2] postgres-package: apply on all
[INF] @psftw/pets/apt·apply: running on all via my-fleet (sudo)
[INF] @psftw/pets/apt·apply: build: applied — install postgresql-18
[INF] @psftw/pets/role·apply: [2/2] postgres-package: applied=1
[INF] @psftw/pets/role·apply: build: applied — pgdg-repo: write /etc/apt/sources.list.d/pgdg-repo.sources; pgdg-repo: fetch key to /etc/apt/keyrings/pgdg.asc; postgres-package: install postgresql-18
[INF] @psftw/pets/role·apply: summary[all]: appliedView the compliance report:
swamp report get @psftw/pets/fleet-compliance --model postgres-roleFleet compliance — postgres-role · apply (succeeded)
Aggregate: applied across 1 host(s) — selector all —
2026-08-11T00:00:00.000Z
| Host | Status | Changes |
|---|---|---|
| build | applied |
3 |
Members
| Host | Member | Status |
|---|---|---|
| build | pgdg-repo | applied |
| build | postgres-package | applied |
Changes
- build: pgdg-repo: write /etc/apt/sources.list.d/pgdg-repo.sources
- build: pgdg-repo: fetch key to /etc/apt/keyrings/pgdg.asc
- build: postgres-package: install postgresql-18
Fix: template-file apply converged owner/group/mode silently when the content
hash already matched, reporting compliant with no changes recorded. It now
touches owner/mode only when drifted, reports the run as applied, and records
each chown/chmod in changes; apply facts gained a performed list. changed
still means content changed, so onChange semantics are unaffected.
Global Arguments
| Argument | Type | Description |
|---|---|---|
| fleet | string | @swamp/ssh model providing inventory and transport |
| requireCharDevices | array | Character devices that must exist; apply refuses without them |
| members | array | Granular @psftw/pets model instances (each implementing check and apply), in execution order |
| Argument | Type | Description |
|---|---|---|
| hosts | string | Fleet host selector (name:X or tag:Y) |
| Argument | Type | Description |
|---|---|---|
| hosts | string | Fleet host selector (name:X or tag:Y) |
Resources
Global Arguments
| Argument | Type | Description |
|---|---|---|
| fleet | string | @swamp/ssh model providing inventory and transport |
| packages | array | Packages that must be installed |
| Argument | Type | Description |
|---|---|---|
| hosts | string | Fleet host selector (name:X or tag:Y) |
| Argument | Type | Description |
|---|---|---|
| hosts | string | Fleet host selector (name:X or tag:Y) |
Resources
Global Arguments
| Argument | Type | Description |
|---|---|---|
| fleet | string | @swamp/ssh model providing inventory and transport |
| name | string | Filename stem for /etc/apt/sources.list.d/<name>.sources |
| uris | array | Repository base URLs |
| suites | array | Distribution suites (e.g. trixie-pgdg) |
| components | array | Repository components (e.g. main) |
| architectures? | array | Restrict to these architectures |
| gpgKeyUrl? | string | Key fetched to signedBy when absent (.asc target: stored armored as-is) |
| signedBy? | string | Keyring path referenced by Signed-By |
| Argument | Type | Description |
|---|---|---|
| hosts | string | Fleet host selector (name:X or tag:Y) |
| Argument | Type | Description |
|---|---|---|
| hosts | string | Fleet host selector (name:X or tag:Y) |
Resources
Global Arguments
| Argument | Type | Description |
|---|---|---|
| fleet | string | @swamp/ssh model providing inventory and transport |
| dirs | array | Directories that must exist |
| Argument | Type | Description |
|---|---|---|
| hosts | string | Fleet host selector (name:X or tag:Y) |
| Argument | Type | Description |
|---|---|---|
| hosts | string | Fleet host selector (name:X or tag:Y) |
Resources
Global Arguments
| Argument | Type | Description |
|---|---|---|
| fleet | string | @swamp/ssh model providing inventory and transport |
| username | string | User whose membership is managed |
| group | string | Supplementary group the user must belong to |
| Argument | Type | Description |
|---|---|---|
| hosts | string | Fleet host selector (name:X or tag:Y) |
| Argument | Type | Description |
|---|---|---|
| hosts | string | Fleet host selector (name:X or tag:Y) |
Resources
Global Arguments
| Argument | Type | Description |
|---|---|---|
| fleet | string | @swamp/ssh model providing inventory and transport |
| service | string | Unit name (e.g. nginx or getty@tty1) |
| enabled | boolean | Unit must be enabled at boot |
| ensure | enum | Desired active state |
| Argument | Type | Description |
|---|---|---|
| hosts | string | Fleet host selector (name:X or tag:Y) |
| Argument | Type | Description |
|---|---|---|
| hosts | string | Fleet host selector (name:X or tag:Y) |
Resources
Global Arguments
| Argument | Type | Description |
|---|---|---|
| fleet | string | @swamp/ssh model providing inventory and transport |
| path | string | Destination file on the host |
| templatePath | string | Liquid template, relative to the repo root |
| owner | string | File owner |
| group | string | File group |
| mode | string | Octal mode (e.g. "0644") |
| variables | record | Extra render-context values, merged with per-host `host` |
| validateCommand? | string | Run against the staged file as $FILE before install (e.g. nft -c -f "$FILE") |
| onChange? | string | Run after install, only when the content hash changed |
| Argument | Type | Description |
|---|---|---|
| hosts | string | Fleet host selector (name:X or tag:Y) |
| Argument | Type | Description |
|---|---|---|
| hosts | string | Fleet host selector (name:X or tag:Y) |
Resources
Per-host role compliance: aggregate, member statuses, and changes
Fix: template-file and apt-repository apply encoded staged content with bare
btoa, which throws on non-Latin1 characters (e.g. an em dash in a template)
and would corrupt 0x80–0xFF characters relative to the UTF-8 content hash.
Content is now base64-encoded over UTF-8 bytes.
- Has README or module doc2/2earned
- README has a code example1/1earned
- README is substantive1/1earned
- Most symbols documented1/1earned
- No slow types (deprecated)1/1earned
- Dependencies pass trust audit2/2earned
- Has description1/1earned
- Platform support declared (or universal)2/2earned
- License1/2partial
no SPDX license declared
- Verified public repository2/2earned