PhilandClaude Opus 5.5 f8197418d3 Remove the last Gondulf references
The gondulf DNS record is deleted (checked against public DNS), so its
removed_records entry is no longer needed. vault_gondulf was also removed
from the vault.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 12:06:35 -06:00
2026-10-31 12:06:35 -06:00
2026-10-31 12:06:35 -06:00
2024-08-13 20:18:11 -06:00
2024-08-13 20:18:11 -06:00

Personal Infrastructure Ansible Playbook

Ansible that builds and runs a personal self-hosted stack: 24 Docker services on one server, served on thesatelliteoflove.com and nerder.land, with Caddy for HTTPS and Pocket ID (passkeys) for single sign-on.

The server

There is one server (hostname cogitator-01, a netcup VPS). The inventory lists it twice, under two names:

Inventory name Address Used by
netcup (group bootstrap) 152.53.36.98 (public IP) bootstrap.yml, for first-time setup before Tailscale is up
docker-01 (group docker) 100.70.169.99 (Tailscale IP) site.yml, for everything after that

All public DNS records point at the public IP. Caddy on the same machine takes ports 80/443 and passes each subdomain to the right container.

Services

Category Service Address Login
Infrastructure Caddy (reverse proxy, static sites) — —
Pocket ID (passkey OIDC provider) id.thesatelliteoflove.com passkeys
Dockge (compose stack manager) port 5001, no public route local
Development Gitea + Actions runner git.thesatelliteoflove.com (SSH on port 222) Pocket ID
ByteStash (code snippets) snippets.thesatelliteoflove.com Pocket ID
Media Audiobookshelf audio.thesatelliteoflove.com Pocket ID
Kavita (ebooks, OPDS, KOReader sync) books.thesatelliteoflove.com Pocket ID
Karakeep (bookmarks, formerly Hoarder) bookmarks.thesatelliteoflove.com Pocket ID
Manyfold (3D models) models.thesatelliteoflove.com Pocket ID
Productivity Paperless-ngx (documents) paper.thesatelliteoflove.com Pocket ID
Baikal (CalDAV/CardDAV) cal.thesatelliteoflove.com local
Syncthing Tailscale only, port 8384 local
HeyForm (forms) forms.nerder.land local
Dawarich (location history) loclog.thesatelliteoflove.com local
Obsidian LiveSync (CouchDB) Tailscale only, port 5984 local
Gokapi (file sharing) gokapi.thesatelliteoflove.com Pocket ID
Communication GoToSocial (fediverse) social.thesatelliteoflove.com Pocket ID
Listmonk (mailing lists) listmonk.nerder.land local
StarPunk (Micropub for the main site) starpunk.thesatelliteoflove.com Pocket ID
Monitoring Changedetection watcher.thesatelliteoflove.com local
Apprise API (notifications) Tailscale only, port 8000 —
Gotify + iGotify assistant gotify.thesatelliteoflove.com, gotify-assistant.thesatelliteoflove.com local
Cronmaster (cron UI) Tailscale only, port 40123 local

"Tailscale only" means the port is bound to the Tailscale IP and has no public address.

Caddy also serves static sites from /opt/stacks/caddy/site/ (the files are not managed by Ansible): the apex thesatelliteoflove.com (plus WebFinger for the fediverse and Tailscale login), pdg.thesatelliteoflove.com, nerder.land and repair.nerder.land. repaircafe.nerder.land redirects to the Listmonk signup form.

Layout

├── site.yml                 # Main playbook: common, docker and cron roles
├── bootstrap.yml            # First-time server setup (user, SSH key, UFW, Tailscale)
├── dns.yml                  # Route53 A records (adds current ones, removes retired ones)
├── deploy-guard.yml         # Stops real runs from anything but a clean origin/main
├── migrate-lava-network.yml # One-off: recreate the shared docker network
├── hosts.yml                # Inventory
├── requirements.yml         # Galaxy roles (artis3n.tailscale)
├── bin/
│   ├── worktree             # Start a task in its own git worktree
│   ├── deploy               # Run a playbook while holding the deploy lock
│   └── vault-run            # Edit vault.yml safely
├── group_vars/all/
│   ├── domains.yml          # Domains and subdomain map
│   ├── infrastructure.yml   # Docker network, IPs, SMTP, paths
│   └── vault.yml            # Encrypted secrets (gitignored)
└── roles/
    ├── bootstrap/           # First-time setup
    ├── common/              # Base packages and UFW
    ├── docker/              # Docker, shell setup and every service
    └── cron/                # Host cron jobs (dockcheck, Warhammer feed)

Role details: bootstrap, common, docker, cron.

Usage

Setup

The bin/ helpers use the virtualenv at .venv/ in the main checkout. The ansible package brings the collections used here (community.docker, community.general, amazon.aws, ansible.posix); boto3 is needed for Route53.

python -m venv .venv && .venv/bin/pip install ansible boto3
.venv/bin/ansible-galaxy install -r requirements.yml

vault_pass and group_vars/all/vault.yml are gitignored and live only in the main checkout. bin/worktree links them into each worktree.

Making a change

Several agents work on this repo at once, so every change follows the same steps (see CLAUDE.md for why):

bin/worktree my-change                 # new worktree off origin/main
# ...edit, commit...
git fetch && git rebase origin/main
git push origin HEAD:main
bin/deploy --tags <service>            # deploy that exact commit

Deploying

bin/deploy --tags kavita               # one service
bin/deploy --tags media,productivity   # whole categories
bin/deploy --tags services             # every service, no system setup
bin/deploy                             # everything in site.yml
bin/deploy --check --diff --tags caddy # dry run (allowed from any checkout)
PLAYBOOK=dns.yml bin/deploy            # DNS records

Category tags: infrastructure, development, media, productivity, communication, monitoring. Role tags: common, docker, cron. Each service has its own tag, named after its task file (e.g. paperlessngx, obsidian-livesync); Karakeep answers to both karakeep and hoarder.

Bootstrapping a new server

.venv/bin/ansible-playbook bootstrap.yml -i hosts.yml --vault-password-file vault_pass

Secrets

All secrets live in group_vars/all/vault.yml under vault_<service> keys. Edit them only with bin/vault-run <command>, never ansible-vault edit.

Security notes

  • UFW allows SSH from anywhere. ufw-docker blocks container ports from the internet except Caddy (80/443) and Gitea SSH (222).
  • Admin tools (Syncthing, Cronmaster, Apprise, CouchDB) bind to the Tailscale IP only. Dockge has no public route.
  • Pocket ID is the single sign-on for most apps. Authentik was retired on 2026-10-31.
  • Paperless-ngx and GoToSocial back up their volumes daily to S3 (7-day retention). Other services have no automated backups yet (see todo.md).
S
Description
No description provided
Readme
567 KiB
Languages
Jinja 68.8%
Shell 31.2%