Files
server_cloud_ladkau_de/CLAUDE.md
T
ml 8adbd3fac4 Add Clay agent interface, fix registry UI routing, and misc improvements
- Deploy Clay (claude Code web UI) at agent.ladkau.de behind basic auth;
  Traefik proxies to clay's HTTPS port 2633 with insecureSkipVerify
- Document MCP server persistence: binaries need to be baked into the
  Dockerfile, config in /opt/clay/data survives rebuilds
- Document scheduled agent workflows via Gitea Actions on.schedule with
  email reporting via Gmail SMTP
- Fix registry UI: split /v2/ (registry) and / (UI) into separate Traefik
  routers; add registry_internal network
- Add weekly registry GC cron job (/usr/local/bin/registry-gc)
- Remove rate-limit middleware from Gitea router (act_runner polling
  exceeded 60 req/min limit)
- Set Traefik websecure readTimeout: 0 to fix large layer upload 499s
2026-08-01 07:55:20 +02:00

109 lines
5.7 KiB
Markdown

# 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/<name>/templates/docker-compose.yml.j2`
- Ansible renders it and writes it to `/opt/<name>/docker-compose.yml` on the server
- Data/config for each service lives under `/opt/<name>/` on the server
- The role also writes any config files (e.g. `dovecot.conf`, `passwd`, `fetchmailrc`) to `/opt/<name>/` 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
### Clay (Claude Code web interface)
Clay (`agent.ladkau.de`) provides a browser-based UI for Claude Code. The role
builds a Docker image from `ansible/roles/clay/files/Dockerfile` (Node 20 +
`@anthropic-ai/claude-code` + `clay-server`) at deploy time. Key details:
- `ANTHROPIC_API_KEY` is passed as an environment variable from the vault
- Sessions and config are persisted at `/opt/clay/data``/root/.clay` in container
- Projects (git repos) are mounted from `/opt/clay/workspace``/workspace`
- Traefik basic auth (`clay-auth` middleware) uses `clay_users` from the vault
- Scheduled agent workflows use Gitea Actions `on.schedule` + the existing act_runners
### Gitea Actions runners
The `act_runner` role deploys three `gitea/act_runner` containers (Docker executor). Each runner handles one concurrent job. Key details:
- Runners connect to Gitea via the public HTTPS URL — no internal Docker network needed
- Each runner gets its own data dir (`/opt/act_runner/runner-N/`) for its `.runner` registration file
- All runners share a single `config.yml` at `/opt/act_runner/config.yml`
- `/var/run/docker.sock` is mounted — job containers are spawned directly on the host
- `gitea_runner_registration_token` is obtained from Gitea admin UI **after** Gitea is running; add it to the vault and then deploy with `--tags act_runner`
### 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++