v0.327.1: files.<domain> reaches FileBrowser again — a file route by name, not Docker labels
gates / gates (push) Successful in 43s
gates / gates (push) Successful in 43s
v0.327.0's box walk: files.<domain> timed out on demo-hp and the N100. With labels, Traefik kept the address it chose at container start (on felhom-filebrowser, before the gate joined traefik-public). FileBrowser now carries no Traefik labels; dynamic/filebrowser.yml routes files.<domain> to http://filebrowser:80 by name, resolved per connection and only while the gate keeps it on traefik-public. Test extended, red-proved. Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -245,13 +245,11 @@ services:
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 15s
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.filebrowser.rule=Host(`+"`"+`files.%s`+"`"+`)"
|
||||
- "traefik.http.routers.filebrowser.entrypoints=websecure"
|
||||
- "traefik.http.routers.filebrowser.tls=true"
|
||||
- "traefik.http.services.filebrowser.loadbalancer.server.port=80"
|
||||
- "traefik.docker.network=traefik-public"
|
||||
# No Traefik labels (v0.327.1): the route is a file-provider route (RenderFileBrowserRoute) to http://filebrowser:80
|
||||
# BY NAME. Docker labels made Traefik pick FileBrowser's address once, at container start — on its own network,
|
||||
# before the box joined it to traefik-public — and a later "network connect" did not refresh it (demo-hp and the
|
||||
# N100 timed out after v0.327.0). By name, the address is resolved per connection, and only while FileBrowser is on
|
||||
# traefik-public.
|
||||
|
||||
volumes:
|
||||
filebrowser_data:
|
||||
@@ -259,7 +257,30 @@ volumes:
|
||||
networks:
|
||||
%s:
|
||||
name: %s
|
||||
`, domain, FileBrowserImage, groupSection, storageSection, FileBrowserNetwork, domain, FileBrowserNetwork, FileBrowserNetwork)
|
||||
`, domain, FileBrowserImage, groupSection, storageSection, FileBrowserNetwork, FileBrowserNetwork, FileBrowserNetwork)
|
||||
}
|
||||
|
||||
// RenderFileBrowserRoute is the traefik file-provider route for files.<domain> → http://filebrowser:80 by NAME (R-1126,
|
||||
// v0.327.1). The name resolves only while FileBrowser is on traefik-public, which the box allows only after this run's
|
||||
// default login was checked — so the route can stay in place and still never reach an unchecked FileBrowser (502 then).
|
||||
// TLS as the old Docker label had it (`tls: {}`, the default certificate store / the wildcard).
|
||||
func RenderFileBrowserRoute(domain string) string {
|
||||
return fmt.Sprintf(`# Traefik dynamic route for FileBrowser (files.<domain>) — managed by felhom-controller (R-1126).
|
||||
# WARNING: auto-generated at base-infra bring-up. Manual edits are overwritten.
|
||||
http:
|
||||
routers:
|
||||
filebrowser:
|
||||
rule: "Host(`+"`"+`files.%s`+"`"+`)"
|
||||
entryPoints:
|
||||
- websecure
|
||||
service: filebrowser
|
||||
tls: {}
|
||||
services:
|
||||
filebrowser:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://filebrowser:80"
|
||||
`, domain)
|
||||
}
|
||||
|
||||
// RenderControllerRoute returns a traefik file-provider dynamic config routing the controller's own
|
||||
|
||||
@@ -216,8 +216,8 @@ func TestControllerRoute(t *testing.T) {
|
||||
|
||||
func TestFileBrowserRender(t *testing.T) {
|
||||
compose := RenderFileBrowserCompose("demo-felhom.eu", nil)
|
||||
if !strings.Contains(compose, "Host(`files.demo-felhom.eu`)") {
|
||||
t.Errorf("domain not wired into filebrowser routing label: %q", compose)
|
||||
if !strings.Contains(compose, "# Domain: files.demo-felhom.eu") {
|
||||
t.Errorf("domain not named in the filebrowser compose header: %q", compose)
|
||||
}
|
||||
if !strings.Contains(compose, FileBrowserImage) {
|
||||
t.Errorf("expected pinned filebrowser image %q", FileBrowserImage)
|
||||
@@ -249,8 +249,10 @@ func TestFileBrowserRender(t *testing.T) {
|
||||
}
|
||||
|
||||
// R-1126 (D11 = A): FileBrowser starts OFF traefik-public — the box joins it there only after this run's default login
|
||||
// is checked — and on its own network, which the controller joins for that check. The Traefik labels stay, so the route
|
||||
// appears the moment the box joins it. RED-PROOF: the service back on traefik-public → this fails.
|
||||
// is checked — and on its own network, which the controller joins for that check. v0.327.1: NO Traefik labels — the
|
||||
// route is the file-provider route by NAME (a label route kept the address Traefik chose at container start, on the
|
||||
// wrong network, and timed out on demo-hp and the N100). RED-PROOF: the service back on traefik-public, or the labels
|
||||
// back → this fails.
|
||||
func TestFileBrowserRender_R1126_NotPublicAtStart(t *testing.T) {
|
||||
compose := RenderFileBrowserCompose("demo-felhom.eu", nil)
|
||||
svc := compose[strings.Index(compose, " filebrowser:"):strings.Index(compose, "\nvolumes:")]
|
||||
@@ -262,7 +264,13 @@ func TestFileBrowserRender_R1126_NotPublicAtStart(t *testing.T) {
|
||||
if !strings.Contains(nets, "- "+FileBrowserNetwork) || !strings.Contains(compose, "name: "+FileBrowserNetwork) {
|
||||
t.Fatalf("FileBrowser must sit on %s (named), got:\n%s", FileBrowserNetwork, compose)
|
||||
}
|
||||
if !strings.Contains(compose, "traefik.docker.network=traefik-public") || !strings.Contains(compose, "Host(`files.demo-felhom.eu`)") {
|
||||
t.Fatal("the Traefik labels must stay, so joining traefik-public is all that publishes it")
|
||||
if strings.Contains(compose, "traefik.") {
|
||||
t.Fatalf("FileBrowser must carry no Traefik labels (the route is the file route by name):\n%s", compose)
|
||||
}
|
||||
route := RenderFileBrowserRoute("demo-felhom.eu")
|
||||
for _, w := range []string{"Host(`files.demo-felhom.eu`)", `url: "http://filebrowser:80"`, "websecure", "tls: {}"} {
|
||||
if !strings.Contains(route, w) {
|
||||
t.Errorf("the file route misses %q:\n%s", w, route)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,11 @@ func (m *Manager) EnsureBaseStack() error {
|
||||
if err := m.wireController(traefikDir); err != nil {
|
||||
errs = append(errs, fmt.Sprintf("controller-route: %v", err))
|
||||
}
|
||||
// R-1126 (v0.327.1): files.<domain> as a file-provider route by NAME — published only while the gate keeps
|
||||
// FileBrowser on traefik-public.
|
||||
if err := m.ensureFileBrowserRoute(traefikDir); err != nil {
|
||||
errs = append(errs, fmt.Sprintf("filebrowser-route: %v", err))
|
||||
}
|
||||
|
||||
if m.cfg.Infrastructure.CFTunnelToken != "" {
|
||||
// cloudflared moves to the tunnel network only once traefik is on it (it reaches traefik by name there); while
|
||||
@@ -279,6 +284,28 @@ func (m *Manager) ensureFileBrowser(dir string) error {
|
||||
return m.composeUp(dir)
|
||||
}
|
||||
|
||||
// ensureFileBrowserRoute writes traefik's dynamic route for files.<domain> (infra.RenderFileBrowserRoute), only when its
|
||||
// content changes, like wireController. No domain → nothing (no route without a name to serve).
|
||||
func (m *Manager) ensureFileBrowserRoute(traefikDir string) error {
|
||||
domain := m.cfg.Customer.Domain
|
||||
if domain == "" {
|
||||
return nil
|
||||
}
|
||||
dynDir := filepath.Join(traefikDir, "dynamic")
|
||||
if err := os.MkdirAll(dynDir, 0o755); err != nil {
|
||||
return fmt.Errorf("mkdir dynamic: %w", err)
|
||||
}
|
||||
path := filepath.Join(dynDir, "filebrowser.yml")
|
||||
want := infra.RenderFileBrowserRoute(domain)
|
||||
if cur, err := os.ReadFile(path); err != nil || string(cur) != want {
|
||||
if err := os.WriteFile(path, []byte(want), 0o644); err != nil {
|
||||
return fmt.Errorf("write filebrowser route: %w", err)
|
||||
}
|
||||
m.logger.Printf("[INFO] [infra] wrote filebrowser route → %s (Host files.%s → filebrowser:80 by name, R-1126)", path, domain)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureServersTransports writes the traefik file-provider dynamic config defining named backend
|
||||
// transports (the insecure-skip-verify transport for self-signed HTTPS backends like Crafty). Like
|
||||
// wireController, it writes only when the content changes so the traefik file watcher doesn't reload
|
||||
|
||||
Reference in New Issue
Block a user