Files
ansible/README.md
T
PhilandClaude Opus 5.5 6dfb58d3f1 Retire Gondulf and move StarPunk to v2.0.0 with Pocket ID login
StarPunk 2.0.0 logs in with Pocket ID and issues its own Micropub tokens
(ADR-064 in the StarPunk repo), so the Gondulf IndieAuth server is no
longer used. Removes its task, templates, Caddy route, subdomain and
service entry, and moves its DNS record to removed_records.

The StarPunk template was still on v1.6.0-rc.2 with Gondulf hairpin
hosts; deploying it would have broken login. It now matches production:
v2.0.0, Pocket ID hairpin, OIDC client from vault_starpunk.pocketid and
the admin sub in starpunk_admin_sub.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:52:07 -06:00

154 lines
6.8 KiB
Markdown

# 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](roles/bootstrap/README.md),
[common](roles/common/README.md), [docker](roles/docker/README.md),
[cron](roles/cron/README.md).
## 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.
```bash
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](CLAUDE.md) for why):
```bash
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
```bash
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
```bash
.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](todo.md)).