From d2a12ca261d7527d4b7764491d8137313bb67156 Mon Sep 17 00:00:00 2001 From: ml Date: Wed, 1 Jul 2026 06:48:07 +0200 Subject: [PATCH] Add CLAUDE.md with codebase guidance for Claude Code --- CLAUDE.md | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..286115f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,86 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Purpose + +Infrastructure-as-code for a Strato VPS at cloud.ladkau.de. Every config change must be committed — the git history is the change log. The goal is a fully reproducible reinstall from this repo alone. + +## Key commands + +```bash +# Install Ansible collections (one-time, after cloning) +ansible-galaxy collection install -r ansible/requirements.yml + +# Run the full playbook (most common operation) +ansible-playbook -i ansible/inventory.ini ansible/site.yml --ask-vault-pass + +# Run a single role +ansible-playbook -i ansible/inventory.ini ansible/site.yml --tags mail --ask-vault-pass + +# Bootstrap a fresh server (before Ansible can run — creates the deploy user) +bash scripts/run-bootstrap.sh + +# Verify all vault secrets are present and valid +bash scripts/check-vault.sh + +# Verify all services are reachable +bash scripts/check-services.sh + +# Edit encrypted secrets +ansible-vault edit ansible/group_vars/all/vault.yml + +# Connect to server +ssh -i keys/notroot_cloud_ladkau_de deploy@217.154.207.148 +``` + +Local dependencies: `pip install ansible passlib` and `openssl`. + +## Architecture + +### Provisioning flow + +1. `scripts/run-bootstrap.sh` — copies and runs `bootstrap-deploy-user.sh` on the server as root; creates the `deploy` user with the SSH public key from `keys/notroot_cloud_ladkau_de.pub` +2. `ansible-playbook ansible/site.yml` — runs all roles in order against the `deploy` user (passwordless sudo) + +### Role → service → data directory + +Each role deploys one Docker Compose stack. The pattern is: + +- **Template** lives in `roles//templates/docker-compose.yml.j2` +- Ansible renders it and writes it to `/opt//docker-compose.yml` on the server +- Data/config for each service lives under `/opt//` on the server +- The role also writes any config files (e.g. `dovecot.conf`, `passwd`, `fetchmailrc`) to `/opt//` before starting the stack + +### Networking + +Two Docker networks: +- `traefik_public` (external, created once by the `traefik` role) — all services that need HTTPS exposure join this network alongside Traefik +- Per-service internal networks (e.g. `mail_internal`) — isolate backend containers (databases, Dovecot) from the internet + +Traefik routes HTTP/HTTPS/IMAPS via Docker labels on each service container. TLS certificates are issued via Let's Encrypt ACME. IMAPS (993) is handled as a TCP passthrough — Traefik terminates TLS and forwards plain IMAP to Dovecot port 143. + +### Secrets + +All secrets live in `ansible/group_vars/all/vault.yml` (Ansible Vault, AES-256). Plain variables are in `ansible/group_vars/all/vars.yml` — commented-out keys there show what each role expects from the vault. `scripts/check-vault.sh` validates all required keys are present. + +Passwords that need bcrypt hashes (Traefik dashboard, container registry) are stored as plaintext in the vault and hashed in the Jinja2 templates at deploy time using `password_hash('bcrypt', ...)`. The `passlib` Python package is required locally for this. + +### Mail stack specifics + +The mail role deploys four containers on `mail_internal`: `mail-db` (PostgreSQL for Roundcube), `dovecot` (IMAP server), `roundcube` (webmail at mail.ladkau.de), and conditionally `fetchmail` (polls external POP3 accounts). + +- Dovecot uses passwd-file auth: `roles/mail/templates/passwd.j2` generates `/opt/mail/passwd` with 8-field format (`user:hash::::::`) — the trailing `::::::` is required for userdb lookup +- Fetchmail delivers via LMTP to Dovecot port 24; the `auth_username_format = %Ln` in Dovecot's LMTP config strips the `@dovecot` domain that fetchmail appends +- Fetchmail 6.6.x rcfile syntax: `ssl` is a user-level option (inline after `password`), port is `smtphost dovecot/24` (slash-separated), `smtpport` keyword does not exist +- `mail.ladkau.de` is the Roundcube webmail client, not an MTA — no outgoing SMTP server is configured + +### SSO + +Keycloak at `sso.ladkau.de` is the identity provider. Gitea and Nextcloud are configured post-provisioning to use it (see `docs/runbook-configuration.md`). + +## Runbooks + +- `docs/runbook-provisioning.md` — step-by-step reinstall from scratch +- `docs/runbook-configuration.md` — first-run service configuration (Keycloak realm, Gitea admin, Nextcloud OIDC, etc.) +- `docs/runbook-mail-import.md` — migrating mbox-format mail from an old Dovecot server to Maildir++