v0.291.0: decision 78 (R-726) — a returning household's first night sets the orphaned copy aside; R-838 — traefik v3.7.13, cloudflared 2026.9.3, filebrowser 1.5.6-stable, a release now moves a running file browser, check-infra-pins.py
gates / gates (push) Successful in 31s
gates / gates (push) Successful in 31s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/dockerexec"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -250,7 +251,7 @@ func changedInfraFiles(dir string, files map[string]infra.FileSpec) []string {
|
||||
|
||||
func (m *Manager) ensureFileBrowser(dir string) error {
|
||||
if containerRunning("filebrowser") {
|
||||
return nil
|
||||
return m.reconcileFileBrowserImage(dir)
|
||||
}
|
||||
composePath := filepath.Join(dir, "docker-compose.yml")
|
||||
if _, err := os.Stat(composePath); err == nil {
|
||||
@@ -411,3 +412,27 @@ func containerRunning(name string) bool {
|
||||
}
|
||||
return strings.TrimSpace(string(out)) == "true"
|
||||
}
|
||||
|
||||
var composeImageLine = regexp.MustCompile(`(?m)^(\s*image:\s*)(\S+)\s*$`)
|
||||
|
||||
// reconcileFileBrowserImage moves a RUNNING file browser to the pinned image (R-838, controller v0.291.0). Its compose
|
||||
// file is owned by web.SyncFileBrowserMounts (the storage mounts), which runs only when storage changes — so before
|
||||
// this, a release that raised FileBrowserImage never reached an installed box. Only the `image:` line is replaced;
|
||||
// the mounts stay byte-for-byte. Same image (or no compose file) → nothing. Pinned by TestReconcileFileBrowserImage_*.
|
||||
func (m *Manager) reconcileFileBrowserImage(dir string) error {
|
||||
composePath := filepath.Join(dir, "docker-compose.yml")
|
||||
cur, err := os.ReadFile(composePath)
|
||||
if err != nil {
|
||||
return nil // nothing provisioned by us here: leave it
|
||||
}
|
||||
mm := composeImageLine.FindSubmatch(cur)
|
||||
if mm == nil || string(mm[2]) == infra.FileBrowserImage {
|
||||
return nil
|
||||
}
|
||||
m.logger.Printf("[INFO] [infra] filebrowser runs %s, the pin is %s — moving it (mounts unchanged; the file browser pauses a few seconds)", mm[2], infra.FileBrowserImage)
|
||||
out := composeImageLine.ReplaceAll(cur, []byte("${1}"+infra.FileBrowserImage))
|
||||
if err := os.WriteFile(composePath, out, 0o644); err != nil {
|
||||
return fmt.Errorf("write docker-compose.yml: %w", err)
|
||||
}
|
||||
return m.composeUp(dir)
|
||||
}
|
||||
|
||||
@@ -255,3 +255,30 @@ func TestEnsureBaseStack_TunnelOrder(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// R-838: a release that raises FileBrowserImage reaches a RUNNING file browser — only the image line changes (the
|
||||
// storage mounts SyncFileBrowserMounts wrote stay byte-for-byte) and it is recreated once. Red-proof: make
|
||||
// ensureFileBrowser return nil for a running container again and the first sub-step fails.
|
||||
func TestReconcileFileBrowserImage_MovesOnlyTheImage(t *testing.T) {
|
||||
state := stubDocker(t)
|
||||
touch(t, filepath.Join(state, "running-filebrowser"), "")
|
||||
m, calls := tunnelTestManager(t, "")
|
||||
dir := t.TempDir()
|
||||
old := strings.Replace(infra.RenderFileBrowserCompose("example.hu", []string{" - /mnt/felhom-drives/d1/userdata:/srv/d1"}), infra.FileBrowserImage, "gtstef/filebrowser:1.3.3-stable", 1)
|
||||
touch(t, filepath.Join(dir, "docker-compose.yml"), old)
|
||||
if err := m.ensureFileBrowser(dir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, _ := os.ReadFile(filepath.Join(dir, "docker-compose.yml"))
|
||||
want := strings.Replace(old, "gtstef/filebrowser:1.3.3-stable", infra.FileBrowserImage, 1)
|
||||
if string(got) != want {
|
||||
t.Fatalf("compose after the move:\n%s\nwant only the image line changed:\n%s", got, want)
|
||||
}
|
||||
if len(*calls) != 1 || (*calls)[0].args != "up -d" {
|
||||
t.Fatalf("want one `up -d`, got %+v", *calls)
|
||||
}
|
||||
// Same image → nothing.
|
||||
if err := m.ensureFileBrowser(dir); err != nil || len(*calls) != 1 {
|
||||
t.Fatalf("an up-to-date file browser was touched: err %v calls %+v", err, *calls)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user