Files
server_cloud_ladkau_de/CLAUDE.md
T
2026-07-02 09:02:50 +02:00

5.0 KiB

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

# 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

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++