Docker 29.x does not create DNAT rules for containers only on internal
networks. Add a non-internal 'front' network that outline and n8n join
alongside their internal networks, enabling host port binding to work.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add traefik-auth filter: ban IPs with 10+ HTTP 401/403 in 5 min
- Add forgejo-ssh jail: ban after 3 failed SSH attempts (24h ban)
- Both jails are active; forgejo-ssh already detected 8 real attempts
- Traefik access.log now written to /opt/services/traefik/logs/
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Switch Traefik ACME to dnsChallenge (provider: cloudflare)
- Add *.csrx.ru wildcard cert via tls.stores.default.defaultGeneratedCert
- Pass CLOUDFLARE_DNS_API_TOKEN to Traefik via env_file: .env
- Add Cloudflare IP ranges to forwardedHeaders.trustedIPs (real visitor IPs)
- Fix UFW: allow 172.16.0.0/12 on 80/443 so act_runner can reach Forgejo
- Add A records: auth.csrx.ru, status.csrx.ru, csrx.ru root → 87.249.49.32
Result: one *.csrx.ru cert covers all subdomains, auto-renewed by Traefik.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DNS-01 + wildcard cert requires Cloudflare to be authoritative NS.
Until propagation completes, use httpChallenge on port 80.
Plan after Cloudflare NS is active:
1. Switch back to dnsChallenge in traefik.yml.j2
2. Re-enable tls.stores.default.defaultGeneratedCert in routes.yml.j2
3. Clear acme.json → Traefik issues *.csrx.ru wildcard cert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add tls.stores.default.defaultGeneratedCert in dynamic config:
- Traefik requests one *.csrx.ru + csrx.ru SAN cert via DNS-01
- All existing and future subdomains use this single cert
- No per-service cert issuance wait when adding new services
- Cert auto-renewed by Traefik ~30 days before expiry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
firewall.yml:
- Allow 172.16.0.0/12 and 10.0.0.0/8 on ports 80/443 so act_runner
job containers can reach git.csrx.ru (Forgejo via Traefik)
- Without this, Cloudflare-only rules broke CI/CD pipeline
unattended_upgrades.yml (new):
- Install unattended-upgrades + apt-listchanges
- Configure auto-apply of security patches only (not all updates)
- Auto-clean every 7 days, remove unused deps
- No auto-reboot (manual control over kernel reboots)
base/tasks/main.yml:
- Add unattended_upgrades.yml to task sequence
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Traefik traefik.yml.j2:
- Add forwardedHeaders.trustedIPs with all Cloudflare CIDR ranges
on both web and websecure entrypoints so rate limiting and
CrowdSec see real visitor IPs, not Cloudflare proxy IPs
firewall.yml:
- Replace open HTTP/HTTPS rules with per-CIDR allow rules
scoped to Cloudflare IP ranges only
- Direct access to ports 80/443 bypassing Cloudflare is now blocked
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cannot use comparison operators inside label matchers {}.
Move the > 0 filter outside braces as a scalar filter on the
denominator — idiomatic Prometheus way to exclude unlimited containers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add vault_s3_access_key / vault_s3_secret_key to Ansible Vault
- Expose via s3_access_key / s3_secret_key in all/main.yml
- Add s3_endpoint + s3_bucket to backup role defaults
- Install awscli via apt in backup role tasks
- Extend backup.sh.j2: upload *.gz to S3 after local backup,
prune S3 objects older than backup_retention_days
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Adds monitoring Docker network (internal)
- Prometheus scrapes node-exporter (host metrics) and cAdvisor (containers)
with 30-day retention
- Grafana exposed at dashboard.csrx.ru with pre-provisioned datasource
and two dashboards: Node Exporter Full (1860) and cAdvisor (14282)
- Vault secret: vault_grafana_admin_password
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New Plane stable requires 3 frontend services:
- plane-admin (nginx:80) for /god-mode/ routes
- plane-space (node:3000) for /spaces/ routes
- plane-web (nginx:80) for all other routes
Also add APP/ADMIN/SPACE_BASE_URL env vars to plane-api so the
setup wizard knows where to redirect.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
makeplane/plane-backend:stable now requires:
- AMQP_URL: Celery broker URL (defaults to amqp://localhost, broken)
→ set to redis://plane-redis:6379/ to reuse existing Redis
- GUNICORN_WORKERS: must be set explicitly (empty string causes crash)
→ set to 2
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
makeplane/plane-frontend:stable now uses nginx (not Next.js/node).
Remove `command: node web/server.js` override and update Traefik
port from 3000 to 80.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Job containers run on runner-jobs network (internet only), so they
can't reach forgejo:3000 (backend-only). Use public https://git.csrx.ru
so both runner and job containers can reach Forgejo.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add gitea/act_runner:0.3.0 to docker-compose stack on runner-jobs network
- Add act_runner config template and directory provisioning
- Add FORGEJO_RUNNER_TOKEN to env template
- Add CI deploy SSH public key to authorized_keys via base role
- Create .forgejo/workflows/deploy.yml: syntax-check on PR, deploy on push to master
- Add .claude/launch.json with ansible-playbook configurations
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>