6 Commits

Author SHA1 Message Date
Gonçalo Rodrigues
8d824b3e19 fix(infra): pin MongoDB to 8.0 LTS to avoid ARM64 segfault
mongo:8 resolves to 8.2 which uses tcmalloc-google. That allocator
segfaults (exit 139) when transparent hugepages are disabled, which is
the default on Hetzner kernels. MongoDB 8.0 LTS uses jemalloc and does
not have this issue.

PVC must be deleted before applying since FCV 8.2 data files can't be
opened by 8.0. Finance API seeds admin on startup so no data is lost.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 22:25:25 +01:00
Gonçalo Rodrigues
81e804206d fix(infra): revert to mongo:8, keep cache-size arg removed
mongo:7 can't open data files written by mongo:8 (exit code 62 =
NeedsDowngrade). Stay on mongo:8 — the SIGSEGV was caused by the
--wiredTigerCacheSizeGB=0.25 flag, not the version. Removing the flag
is the actual fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 22:21:11 +01:00
Gonçalo Rodrigues
de48ba2206 fix(infra): switch MongoDB to v7 to fix ARM64 segfault
mongo:8 was crashing with exit code 139 (SIGSEGV) on the Hetzner CAX11
ARM64 instance. Switch to mongo:7 (LTS) which has more stable ARM64
support. Also remove the --wiredTigerCacheSizeGB=0.25 arg since the
512Mi memory limit already bounds memory use adequately.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 22:17:48 +01:00
Gonçalo Rodrigues
8436295bbc feat(infra): gate observability stack behind var.enable_monitoring (#38)
Adds enable_monitoring variable (default true) that controls whether
Prometheus/Grafana, Loki, Fluent Bit, and Jaeger are deployed.
Setting it to false saves ~1.5 GB RAM, making the stack viable on
a 2–4 GB VPS without touching the application services.

Also caps MongoDB WiredTiger cache at 256 MB (--wiredTigerCacheSizeGB=0.25)
so it doesn't balloon on memory-constrained hosts.

Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 17:44:14 +01:00
Gonçalo Rodrigues
05dd725579 feat(infra): Gitea self-hosted CI/CD + MongoDB PVC + registry pipeline (#28)
* fix(k8s): expose / without auth so homepage is publicly reachable

Adds a second Ingress (api-public) for the exact path / with no
forward-auth middleware. Traefik prefers the Exact match for the root,
while the Prefix ingress (with auth) still protects all other routes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: homepage renders correctly at / for unauthenticated visitors

Two fixes:
1. Added parseStandalone() helper — parseTmpl() roots on "" but ParseFS()
   stores standalone (no {{define}}) files under their base filename, so
   Execute() ran the empty root and returned Content-Length: 0.
2. Added router.priority: 100 annotation to api-public ingress so Traefik
   picks the Exact / rule over the Prefix / rule (Traefik ranks by rule
   string length by default, which made PathPrefix beat Path).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(k8s): remove forward-auth middleware from finance ingress

The app now handles its own auth at /auth/login — Traefik no longer
needs to forward-auth requests, which was causing redirects to
auth.homelab.local instead of finance.homelab.local.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(auth): harden authentication for cloud deployment

1. Secure cookie flag — set when BASE_URL starts with https://
2. SameSite=Strict on session cookie (was Lax)
3. Rate limiter — per-IP, 10 failures → 15-min lockout, auto-cleanup goroutine
4. Session rotation on login — old session deleted before issuing new one
   (prevents session fixation attacks)
5. bcrypt cost 12 (was DefaultCost/10, OWASP minimum for cloud)
6. Security headers middleware on all responses:
   X-Content-Type-Options, X-Frame-Options, Referrer-Policy,
   Permissions-Policy, Content-Security-Policy, HSTS (when HTTPS)
7. Structured audit logging — login success/failure/lockout with IP + email
8. Google OAuth state cookie gets Secure flag too

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(infra): Gitea self-hosted CI/CD + MongoDB PVC + registry pipeline

- Add Gitea Helm deployment (git hosting, container registry, Gitea Actions)
- Add act runner with DinD sidecar for Docker builds in-cluster
- Add RBAC so act runner can kubectl-deploy to finance namespace
- Fix MongoDB StatefulSet: add volumeClaimTemplates (data was lost on restart)
- Configure k3d containerd to mirror git.homelab.local → Gitea NodePort 30002
- Add .gitea/workflows/finance-api.yml: test → build/push → rolling deploy
- Update finance-api deployment: Gitea registry image, imagePullPolicy Always
- Extract finance-api secrets (SESSION_SECRET, Google OAuth) into Terraform
- Add variables.tf for Gitea admin password and runner token

All changes testable on local k3d before the VPS exists.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 21:45:34 +01:00
Gonçalo Rodrigues
13b7149614 First Commit 2026-06-13 11:25:23 +01:00