Bring the docs up to date with the running stack
The READMEs, CLAUDE.md and todo.md still described Authentik, services removed long ago (Ghost, MMDL, Postiz, Pinchflat...) and two servers. Now they list the 24 services actually deployed (checked against docker ps), explain that netcup and docker-01 are one machine, document Gondulf, StarPunk, Listmonk, Gokapi and dockcheck, and use bin/deploy throughout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,157 +1,154 @@
|
||||
# Personal Infrastructure Ansible Playbook
|
||||
|
||||
This Ansible playbook automates the setup and management of a personal self-hosted infrastructure running Docker containers for various services.
|
||||
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.
|
||||
|
||||
## Overview
|
||||
## The server
|
||||
|
||||
The playbook manages two main environments:
|
||||
- **Bootstrap server** (`netcup`): Initial server setup with Tailscale VPN
|
||||
- **Docker server** (`docker-01`): Main application server running containerized services
|
||||
There is one server (hostname `cogitator-01`, a netcup VPS). The inventory
|
||||
lists it twice, under two names:
|
||||
|
||||
## Services Deployed
|
||||
| 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 |
|
||||
|
||||
The Docker role deploys and manages 21 self-hosted services organized into logical categories:
|
||||
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.
|
||||
|
||||
### Infrastructure
|
||||
- **Caddy** (Reverse proxy with automatic HTTPS)
|
||||
- **Pocket ID** (passkey SSO/Identity Provider)
|
||||
- **Dockge** (Container management)
|
||||
## Services
|
||||
|
||||
### Development
|
||||
- **Gitea** (Git repository hosting)
|
||||
- **ByteStash** (Code snippet management)
|
||||
| Category | Service | Address | Login |
|
||||
|---|---|---|---|
|
||||
| Infrastructure | Caddy (reverse proxy, static sites) | — | — |
|
||||
| | Pocket ID (passkey OIDC provider) | id.thesatelliteoflove.com | passkeys |
|
||||
| | Gondulf (IndieAuth for the main site) | gondulf.thesatelliteoflove.com | email code |
|
||||
| | 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 | IndieAuth |
|
||||
| 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 |
|
||||
|
||||
### Media
|
||||
- **Audiobookshelf** (Audiobook server)
|
||||
- **Kavita** (E-book server)
|
||||
- **Karakeep** (Bookmark manager, formerly Hoarder)
|
||||
- **Manyfold** (3D model organizer)
|
||||
"Tailscale only" means the port is bound to the Tailscale IP and has no public
|
||||
address.
|
||||
|
||||
### Productivity
|
||||
- **Paperless-NGX** (Document management)
|
||||
- **Baikal** (CalDAV/CardDAV server)
|
||||
- **Syncthing** (File synchronization)
|
||||
- **HeyForm** (Form builder)
|
||||
- **Dawarich** (Location tracking)
|
||||
- **Obsidian LiveSync** (Note synchronization)
|
||||
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.
|
||||
|
||||
### Communication
|
||||
- **GoToSocial** (Fediverse/Mastodon)
|
||||
|
||||
### Monitoring
|
||||
- **Changedetection** (Website change monitoring)
|
||||
- **AppriseAPI** (Notification service)
|
||||
- **Gotify** (Push notifications)
|
||||
- **Cronmaster** (Scheduled container tasks)
|
||||
|
||||
## Structure
|
||||
## Layout
|
||||
|
||||
```
|
||||
├── site.yml # Main playbook
|
||||
├── bootstrap.yml # Server bootstrap playbook
|
||||
├── dns.yml # AWS Route53 DNS management
|
||||
├── hosts.yml # Inventory file
|
||||
├── requirements.yml # External role dependencies
|
||||
├── 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/ # Initial server setup
|
||||
├── common/ # Common server configuration
|
||||
├── cron/ # Scheduled tasks
|
||||
└── docker/ # Docker services deployment
|
||||
├── bootstrap/ # First-time setup
|
||||
├── common/ # Base packages and UFW
|
||||
├── docker/ # Docker, shell setup and every service
|
||||
└── cron/ # Host cron jobs (dockcheck, Warhammer feed)
|
||||
```
|
||||
|
||||
## Roles Documentation
|
||||
|
||||
Each role has detailed documentation in its respective directory:
|
||||
|
||||
### [Bootstrap Role](roles/bootstrap/README.md)
|
||||
Performs initial server setup and hardening:
|
||||
- Creates user accounts with SSH key authentication
|
||||
- Configures passwordless sudo and security hardening
|
||||
- Installs essential packages and configures UFW firewall
|
||||
- Sets up Tailscale VPN for secure network access
|
||||
|
||||
### [Common Role](roles/common/README.md)
|
||||
Provides shared configuration for all servers:
|
||||
- Installs common packages (aptitude)
|
||||
- Enables UFW firewall with default deny policy
|
||||
- Ensures consistent base configuration across infrastructure
|
||||
|
||||
### [Cron Role](roles/cron/README.md)
|
||||
Manages scheduled tasks and automation:
|
||||
- **Warhammer RSS Feed Updater**: Daily job that generates and updates RSS feeds
|
||||
- Integrates with Docker services for content generation
|
||||
- Supports easy addition of new scheduled tasks
|
||||
|
||||
### [Docker Role](roles/docker/README.md)
|
||||
The most comprehensive role, deploying 21 containerized services organized into logical categories:
|
||||
- **Infrastructure**: Caddy reverse proxy, Pocket ID SSO, Dockge management
|
||||
- **Development**: Gitea, ByteStash
|
||||
- **Media**: Audiobookshelf, Kavita, Karakeep, Manyfold
|
||||
- **Productivity**: Paperless-NGX, Baikal, Syncthing, HeyForm, Dawarich, Obsidian LiveSync
|
||||
- **Communication**: GoToSocial
|
||||
- **Monitoring**: Changedetection, AppriseAPI, Gotify, Cronmaster
|
||||
- **Template-Driven**: All services use Jinja2 templates for consistent configuration
|
||||
- **Category-Based Deployment**: Deploy services by category using Ansible tags
|
||||
Role details: [bootstrap](roles/bootstrap/README.md),
|
||||
[common](roles/common/README.md), [docker](roles/docker/README.md),
|
||||
[cron](roles/cron/README.md).
|
||||
|
||||
## Usage
|
||||
|
||||
### Prerequisites
|
||||
### Setup
|
||||
|
||||
1. Install Ansible and required collections:
|
||||
```bash
|
||||
ansible-galaxy install -r requirements.yml
|
||||
```
|
||||
|
||||
2. Configure your inventory in `hosts.yml` with your server details
|
||||
|
||||
### Bootstrap a New Server
|
||||
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
|
||||
ansible-playbook bootstrap.yml -i hosts.yml
|
||||
python -m venv .venv && .venv/bin/pip install ansible boto3
|
||||
.venv/bin/ansible-galaxy install -r requirements.yml
|
||||
```
|
||||
|
||||
This will:
|
||||
- Create a user account
|
||||
- Install and configure Tailscale VPN
|
||||
- Set up basic security
|
||||
`vault_pass` and `group_vars/all/vault.yml` are gitignored and live only in
|
||||
the main checkout. `bin/worktree` links them into each worktree.
|
||||
|
||||
### Deploy Docker Services
|
||||
### 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
|
||||
ansible-playbook site.yml -i hosts.yml
|
||||
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
|
||||
```
|
||||
|
||||
Deploy specific services using tags:
|
||||
```bash
|
||||
# Deploy by service category
|
||||
ansible-playbook site.yml -i hosts.yml --tags infrastructure
|
||||
ansible-playbook site.yml -i hosts.yml --tags media,productivity
|
||||
|
||||
# Deploy all services (skips system setup)
|
||||
ansible-playbook site.yml -i hosts.yml --tags services
|
||||
|
||||
# Deploy individual services
|
||||
ansible-playbook site.yml -i hosts.yml --tags caddy
|
||||
ansible-playbook site.yml -i hosts.yml --tags pocketid
|
||||
```
|
||||
|
||||
### Manage DNS Records
|
||||
### Deploying
|
||||
|
||||
```bash
|
||||
ansible-playbook dns.yml -i hosts.yml
|
||||
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
|
||||
```
|
||||
|
||||
Updates AWS Route53 DNS records for configured domains (`thesatelliteoflove.com` and `nerder.land`).
|
||||
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`.
|
||||
|
||||
## Configuration
|
||||
### Bootstrapping a new server
|
||||
|
||||
- Service configurations are templated in `roles/docker/templates/`
|
||||
- Environment variables and secrets should be managed through Ansible Vault
|
||||
- Docker Compose files are generated from Jinja2 templates
|
||||
```bash
|
||||
.venv/bin/ansible-playbook bootstrap.yml -i hosts.yml --vault-password-file vault_pass
|
||||
```
|
||||
|
||||
## Security Notes
|
||||
### Secrets
|
||||
|
||||
- Uses Tailscale for secure network access
|
||||
- Caddy provides automatic HTTPS with Let's Encrypt
|
||||
- Services are containerized for isolation
|
||||
- UFW firewall rules are managed via Docker integration
|
||||
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)).
|
||||
|
||||
Reference in New Issue
Block a user