v0.41.0: first-boot base-infra bring-up + self-heal (+ Section-G mount fix)

New internal/infra package renders traefik/cloudflared/filebrowser from config
(pinned images, single source of truth; web filebrowser path delegates here).
stacks.EnsureBaseStack deploys the traefik-public network + the three stacks,
single-flight + idempotent + non-fatal; wired to first boot and every health
tick. monitor.EffectiveProtected drops cloudflared when no tunnel token.
Section-G fix lives in felhom-agent build-golden.sh (same-path stacks bind).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-11 14:56:42 +02:00
parent ba0e1eb04a
commit abbd9488c6
13 changed files with 873 additions and 111 deletions
@@ -0,0 +1,22 @@
# Cloudflare Tunnel — external access connector — managed by felhom-controller (base-infra bring-up).
# Routes are configured in the Cloudflare dashboard (Zero Trust > Networks > Tunnels > Public Hostname);
# the tunnel connects Cloudflare's edge to Traefik, which handles TLS + routing internally.
services:
cloudflared:
image: {{.Image}}
container_name: cloudflared
restart: unless-stopped
command: tunnel run
environment:
- TUNNEL_TOKEN={{.CFTunnelToken}}
dns:
- 1.1.1.1
- 8.8.8.8
security_opt:
- no-new-privileges:true
networks:
- traefik-public
networks:
traefik-public:
external: true
@@ -0,0 +1,30 @@
# Traefik Reverse Proxy — managed by felhom-controller (base-infra bring-up).
services:
traefik:
image: {{.Image}}
container_name: traefik
restart: unless-stopped
dns:
- 1.1.1.1
- 8.8.8.8
security_opt:
- no-new-privileges:true
ports:
- "80:80"
- "443:443"
{{- if .CFAPIToken}}
env_file:
- .env
{{- end}}
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik.yml:/etc/traefik/traefik.yml:ro
- ./dynamic:/etc/traefik/dynamic:ro
- ./acme.json:/etc/traefik/acme.json
- ./certs:/etc/traefik/certs:ro
networks:
- traefik-public
networks:
traefik-public:
external: true
@@ -0,0 +1,54 @@
# Traefik Static Configuration
# Generated by felhom-controller (base-infra bring-up). Do not edit — regenerated on bring-up.
api:
dashboard: true
insecure: false
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
{{- if .ACMEEmail}}
http:
tls:
certResolver: letsencrypt
{{- end}}
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: traefik-public
file:
directory: /etc/traefik/dynamic
watch: true
log:
level: INFO
accessLog: {}
{{- if .ACMEEmail}}
certificatesResolvers:
letsencrypt:
acme:
email: {{.ACMEEmail}}
storage: /etc/traefik/acme.json
{{- if .CFAPIToken}}
dnsChallenge:
provider: cloudflare
resolvers:
- "1.1.1.1:53"
- "8.8.8.8:53"
{{- else}}
httpChallenge:
entryPoint: web
{{- end}}
{{- end}}