feat(update): Testing/Stable-Update-Kanäle wie enconf (Suite=trixie, Komponente=Kanal)
Installer (EDGEGUARD_CHANNEL), Kanal-Lesen/-Schreiben ohne DB-State (sources.list ist Quelle der Wahrheit), Cluster-Endpoints für Kanalwechsel mit mTLS-Peer-Propagation + Drift-Erkennung, --allow-downgrades für testing→stable-Downgrades über den bestehenden sicheren Rolling-Update- Flow, Settings-UI mit Bestätigung, neues scripts/release.sh (Testing-Push datumsbasiert YYYY.MM.DD.NN, Stable-Promotion mit Verify-Gate + Git-Tag), publish.sh/cleanup-old.sh kanalfähig mit Stable-Tag-Schutz. Migriert Bestandsnodes automatisch von der alten "main"-Komponente auf "stable" (postinst, idempotent) — ohne das würden vor diesem Release installierte Nodes stillschweigend keine Updates mehr sehen, sobald main nicht mehr bespielt wird. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -237,3 +237,61 @@ func AutoUpdateEnabled() bool {
|
||||
_, err := os.Stat(AutoUpdateConfPath)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// ── Update-Kanal (stable/testing) ──────────────────────────────────────
|
||||
//
|
||||
// Kanal-Modell 1:1 von enconf übernommen: Suite = OS-Codename (trixie),
|
||||
// Komponente = Kanal. Kein eigenes Config-File — die sources.list-Zeile
|
||||
// selbst ist die einzige Quelle der Wahrheit (siehe scripts/install.sh
|
||||
// setup_repo(), das dieselbe Zeile beim Erstinstall schreibt).
|
||||
|
||||
// SourcesListPath: vom Installer angelegte apt-Quelle. Root-owned wie
|
||||
// AutoUpdateConfPath — Schreibzugriff nur via sudo tee (Sudoers-Pin im
|
||||
// postinst).
|
||||
const SourcesListPath = "/etc/apt/sources.list.d/edgeguard.list"
|
||||
|
||||
const sourcesListTemplate = "deb [signed-by=/etc/apt/keyrings/nmg.asc] " +
|
||||
"https://git.netcell-it.de/api/packages/projekte/debian trixie %s\n"
|
||||
|
||||
// Channel liest den aktuell konfigurierten Update-Kanal aus dem letzten
|
||||
// Feld der deb-Zeile. Default "stable" wenn die Datei fehlt oder das
|
||||
// letzte Feld kein bekannter Kanal ist (Fail-safe — nie stillschweigend
|
||||
// "testing" annehmen).
|
||||
func Channel() string {
|
||||
data, err := os.ReadFile(SourcesListPath)
|
||||
if err != nil {
|
||||
return "stable"
|
||||
}
|
||||
for _, raw := range strings.Split(string(data), "\n") {
|
||||
line := strings.TrimSpace(raw)
|
||||
if !strings.HasPrefix(line, "deb ") {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) == 0 {
|
||||
continue
|
||||
}
|
||||
switch fields[len(fields)-1] {
|
||||
case "stable", "testing":
|
||||
return fields[len(fields)-1]
|
||||
}
|
||||
}
|
||||
return "stable"
|
||||
}
|
||||
|
||||
// SetChannel schreibt die sources.list-Zeile mit dem neuen Kanal und
|
||||
// refresht den apt-Cache sofort — sonst zeigt der Update-Banner bis zum
|
||||
// nächsten 5-min-Throttle-Fenster noch den alten Kanal-Stand.
|
||||
func SetChannel(ctx context.Context, channel string) error {
|
||||
if channel != "stable" && channel != "testing" {
|
||||
return fmt.Errorf("apt: unknown channel %q (expected stable|testing)", channel)
|
||||
}
|
||||
body := fmt.Sprintf(sourcesListTemplate, channel)
|
||||
cmd := exec.Command("sudo", "-n", "/usr/bin/tee", SourcesListPath)
|
||||
cmd.Stdin = strings.NewReader(body)
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("sudo tee %s: %w: %s", SourcesListPath, err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
RefreshNow(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user