Expose Forgejo publicly at forge.eblu.me #278

Merged
eblume merged 14 commits from feature/forge-public into main 2026-03-03 08:40:42 -08:00

14 commits

Author SHA1 Message Date
fedfdb1228 Remove Alpine's default SSH jails for fail2ban
Alpine ships alpine-ssh.conf with sshd and sshd-ddos jails enabled.
These fail on startup because there's no SSH server or /var/log/messages
in the container. Remove the file after install instead of trying to
override via [DEFAULT] (per-jail enabled=true beats DEFAULT).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 08:34:21 -08:00
771507e489 Disable all default fail2ban jails, not just sshd
Use [DEFAULT] enabled = false to disable all inherited jails globally.
The previous fix only disabled sshd, but sshd-ddos (and potentially
others) also fail looking for missing log files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 08:31:39 -08:00
52f0308f1a Fix fail2ban crash: disable default sshd jail, make non-fatal
Alpine's fail2ban ships with sshd jail enabled by default. Since there's
no SSH server in the Fly.io container, fail2ban exits with an error
looking for sshd logs — crashing the container via set -e.

Disable the sshd jail explicitly and make fail2ban startup non-fatal
since nginx rate limiting is the primary defense.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 08:29:33 -08:00
dbfe7365d8 Document that ArgoCD excludes Endpoints resources
ArgoCD's resource.exclusions in argocd-cm skips all Endpoints objects
(they're normally auto-managed by the control plane). The manual
forge-external Endpoints must be applied directly with kubectl.

Removed endpoints-forge.yaml from kustomization resources and added
comments in both files explaining the situation and the apply command.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 08:24:24 -08:00
2759675704 Split Endpoints into separate file for kustomize discovery
Kustomize didn't pick up the Endpoints from the multi-document YAML
in svc-forge-external.yaml. Split into a separate endpoints-forge.yaml
and add to kustomization.yaml resources.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 08:19:03 -08:00
17dac7ea27 Fix forge ExternalName → ClusterIP+Endpoints for Tailscale Ingress
The Tailscale ingress operator requires backends with a ClusterIP.
ExternalName services don't have one, causing "invalid ClusterIP"
errors. Replace with a headless Service + manual Endpoints pointing
to indri's Tailscale IP (100.98.163.89).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 08:17:24 -08:00
538a8cf6c1 Rename HTTPS forge.ops.eblu.me → forge.eblu.me across codebase
Update all HTTPS references to use the new public domain. This
touches workflows, ArgoCD manifests, Ansible, mise-tasks, NixOS
config, and documentation (~29 files).

Deliberately kept as forge.ops.eblu.me:
- SSH repoURLs in argocd/apps/ (SSH stays tailnet-only)
- containers/*/Dockerfile and *.nix (internal CI efficiency)
- Caddy services table in routing.md
- Internal URL references in forgejo.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 07:57:48 -08:00
32bf20525f Add forge.eblu.me DNS CNAME and Fly.io TLS certificate
Add CNAME record pointing forge.eblu.me to blumeops-proxy.fly.dev
in Pulumi Gandi config. Add forge.eblu.me to fly-setup cert list.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 07:54:20 -08:00
8f47145b40 Update Authentik Forgejo OAuth callback to forge.eblu.me
Update redirect_uris and meta_launch_url to use the new public domain.
OAuth flow will dead-end naturally since Authentik is not publicly
accessible — SSO only works from the tailnet.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 07:53:28 -08:00
80c33ccaf1 Add forge.eblu.me to Fly.io proxy with rate limiting and fail2ban
nginx configuration:
- forge.eblu.me server block with WebSocket support, 512m body limit
- Rate limit login/signup/forgot-password at 3r/s per real client IP
  (keyed on Fly-Client-IP header, not Fly's internal remote_addr)
- Static asset caching (7d), no blanket caching for dynamic content
- Security headers (HSTS, X-Frame-Options, X-Content-Type-Options)
- Block /swagger (API docs only available via tailnet)
- X-Real-IP set to real client IP for Forgejo audit logs
- geo-based deny list for fail2ban integration

fail2ban configuration:
- Custom filter matching 401/403 on login paths in nginx JSON log
- Ban after 5 failures in 10 minutes, ban duration 1 hour
- Custom nginx-deny action: writes IPs to deny file and reloads nginx
  (iptables won't work in Fly.io — remote_addr is Fly's proxy IP)
- Ban lists ephemeral across deploys (nginx rate limiting is persistent)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 07:52:58 -08:00
b49ff9f821 Add Tailscale Ingress for Forge via ExternalName Service
Create forge.tail8d86e.ts.net endpoint that proxies to Forgejo on
indri:3001. Uses ExternalName Service since Forgejo runs natively
on indri (not in k8s). Tagged with flyio-target for Fly.io proxy
access via existing ACLs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 07:51:28 -08:00
d2da346ac0 Harden Forgejo for public access: domain, proxy trust, registration lockdown
- Set forgejo_domain to forge.eblu.me (public URL in clone URLs)
- Set forgejo_ssh_domain to forge.ops.eblu.me (SSH stays tailnet-only)
- Add REVERSE_PROXY_LIMIT=2, REVERSE_PROXY_TRUSTED_PROXIES=* for
  correct client IP logging through Fly.io + Tailscale proxy chain
- Enable ALLOW_ONLY_EXTERNAL_REGISTRATION to block local signups

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 07:50:25 -08:00
d6584a2bd6 Docs: document forge.eblu.me public access, update routing and security guidance
Update forgejo.md with public access details and security controls.
Add forge.eblu.me to public services table in routing.md.
Update fail2ban guidance in expose-service-publicly.md to reflect
Fly.io container approach. Add changelog fragment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 07:49:49 -08:00
d4082985a6 Remove deprecated forge egress proxy from tailscale-operator
The egress proxy (tailscale-forge device) has been unused since Caddy
took over forge routing. No k8s resources reference it as a backend.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 06:56:33 -08:00