Media build part 1 (unreleased): real restart (R-1026), media apps on every PRESENT drive via an owned compose override (R-1023), dead-drive check from mountinfo, removal never deletes shared/:ro binds (R-1027), safety list knows userdata (R-1032), no drive question for media apps (R-1031)
gates / gates (push) Successful in 46s

Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
admin committed 2026-10-15 09:25:43 +02:00
1 parent 331be17e1e
commit 78c894234c
13 files changed
+984 -9

No files matched your search

+73
View File
@@ -552,3 +552,76 @@ func observationCoversTemplate(observed map[string]InstalledImage, tpl map[strin
}
return true
}
// observeStartTimes maps each compose service of the stack to its container's .State.StartedAt (R-1026). Read through
// the recorder's own process boundary (runInstalled: a context and a timeout). The format carries a literal "started"
// field so an inspect answer of another shape is ignored rather than misread as a time.
func (m *Manager) observeStartTimes(stackDir string, env []string) (map[string]string, error) {
ctx, cancel := context.WithTimeout(context.Background(), installedRecordTimeout)
defer cancel()
bin, argv := m.composeArgv("ps", "-a", "--format", "json")
psOut, err := m.runInstalled(ctx, stackDir, env, bin, argv...)
if err != nil {
return nil, fmt.Errorf("listing containers: %w", err)
}
entries, err := parseComposePS(psOut)
if err != nil {
return nil, err
}
svcOf := map[string]string{}
var ids []string
for _, e := range entries {
if e.Service == "" || e.ID == "" {
continue
}
svcOf[e.ID] = e.Service
ids = append(ids, e.ID)
}
if len(ids) == 0 {
return map[string]string{}, nil
}
sort.Strings(ids)
args := append([]string{"inspect", "--type", "container",
"--format", "{{.Id}}" + inspectSep + "started" + inspectSep + "{{.State.StartedAt}}"}, ids...)
out, err := m.runInstalled(ctx, "", nil, "docker", args...)
if err != nil {
return nil, fmt.Errorf("inspecting containers: %w", err)
}
res := map[string]string{}
for _, line := range strings.Split(strings.TrimSpace(out), "\n") {
parts := strings.Split(strings.TrimSpace(line), inspectSep)
if len(parts) != 3 || parts[1] != "started" {
continue
}
for id, svc := range svcOf {
if parts[0] == id || (len(parts[0]) > 12 && parts[0][:12] == id) {
res[svc] = parts[2]
}
}
}
return res, nil
}
// diffStartTimes: the services whose start time moved ("svc old → new"), and the ones that kept it.
func diffStartTimes(before, after map[string]string) (changed, same []string) {
svcs := make([]string, 0, len(after))
for s := range after {
svcs = append(svcs, s)
}
sort.Strings(svcs)
for _, s := range svcs {
if b, ok := before[s]; ok && b == after[s] {
same = append(same, s)
} else {
changed = append(changed, fmt.Sprintf("%s %s → %s", s, orDash(before[s]), after[s]))
}
}
return changed, same
}
func orDash(s string) string {
if s == "" {
return "-"
}
return s
}