mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-10-21 14:52:05 +02:00
Three ways the installation could still end up with its source, its binary and the operator's content describing different things. Rollback copied aside only *tracked* protected files, so a role upstream had deleted and the operator had recreated at the same path was untracked - and `git reset --hard` wrote the old commit's copy over it. The stash now also covers untracked protected paths the target commit writes to, and names them in keptContent like any other file it saved. The pending-build marker said only "a compile is owed". An update that found new commits therefore merged them and built the *new* tree, leaving `.prev` holding a binary for a commit the state file no longer named - a rollback then paired old source with the original binary. And a marker left behind after a successful swap rebuilt and overwrote that same `.prev`. The marker now names its target commit and the hashes on both sides of the swap, the state records which commit the live binary came from, an owed build is finished before the tree moves again, and a retry that finds the swap already done clears the marker instead of repeating it. Rollback verifies that pairing before touching anything and refuses (binary_changed) rather than reset the source beside a binary it cannot account for; an update whose build failed never swapped one, so its rollback moves the source only and no longer demands a `.prev`. The version pair is recorded before the config is written, so a crash in between still leaves a rollback that can put the old number back.
125 lines
4.1 KiB
Go
125 lines
4.1 KiB
Go
package update
|
|
|
|
import (
|
|
"cyberstrike-ai/internal/config"
|
|
"fmt"
|
|
"path/filepath"
|
|
)
|
|
|
|
// The release version travels with the code: config.example.yaml ships in the repository
|
|
// and upstream bumps its version field in the release commit, but the live config.yaml is
|
|
// the operator's file and no merge ever touches it. So without the steps below an updated
|
|
// installation keeps showing the version it was born with - the header badge and the
|
|
// static-asset cache buster would both say v-old while the tree runs v-new. The fix is
|
|
// deliberately small: read the version the new code carries, write it into the live
|
|
// config's version line (byte-for-byte otherwise untouched, atomic rename), and let a
|
|
// rollback put the old number back with the old code.
|
|
|
|
// liveConfigFile names the operator's configuration inside an install tree: the
|
|
// conventional config.yaml, then config.yml. Empty when neither exists (a source
|
|
// checkout, where there is nothing to sync).
|
|
func liveConfigFile(root string) string {
|
|
for _, name := range []string{"config.yaml", "config.yml"} {
|
|
path := filepath.Join(root, name)
|
|
if fileExists(path) {
|
|
return path
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// installVersion is the release version the code sitting in root carries, read from the
|
|
// shipped config.example.yaml; empty when the tree does not say.
|
|
func installVersion(root string) string {
|
|
version, err := config.FileVersion(filepath.Join(root, "config.example.yaml"))
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return version
|
|
}
|
|
|
|
// syncVersionToConfig writes the code's release version into the live configuration and
|
|
// returns (before, after, error) - both empty when there was nothing to do. A tree
|
|
// without a live config, or without a shipped version, is left alone; a current value
|
|
// that already matches is not rewritten.
|
|
func syncVersionToConfig(root string) (before, after string, err error) {
|
|
path := liveConfigFile(root)
|
|
if path == "" {
|
|
return "", "", nil
|
|
}
|
|
target := installVersion(root)
|
|
if target == "" {
|
|
return "", "", nil
|
|
}
|
|
current, err := config.FileVersion(path)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
if current == target {
|
|
return "", "", nil
|
|
}
|
|
changed, err := config.WriteVersion(path, target)
|
|
if err != nil {
|
|
return current, "", err
|
|
}
|
|
if !changed {
|
|
return "", "", nil
|
|
}
|
|
return current, target, nil
|
|
}
|
|
|
|
// plannedVersionChange reports the change a sync is about to make, before it makes it: the
|
|
// live config's current number and the one the code on disk carries. Empty when there is
|
|
// nothing to do. An update records this in its pending marker *before* writing the config,
|
|
// so a crash between the two still leaves a rollback that can put the old number back.
|
|
func plannedVersionChange(root string) (before, after string) {
|
|
path := liveConfigFile(root)
|
|
if path == "" {
|
|
return "", ""
|
|
}
|
|
target := installVersion(root)
|
|
if target == "" {
|
|
return "", ""
|
|
}
|
|
current, err := config.FileVersion(path)
|
|
if err != nil || current == target {
|
|
return "", ""
|
|
}
|
|
return current, target
|
|
}
|
|
|
|
// versionStep renders the sync outcome as one progress line; no lines for "nothing to
|
|
// do", a warning when the write failed (the update itself still stands).
|
|
func versionStep(step func(string, string), before, after string, err error) {
|
|
if err != nil {
|
|
step("version", "版本号写入 config.yaml 失败(不影响本次更新):"+err.Error())
|
|
return
|
|
}
|
|
if after == "" {
|
|
return
|
|
}
|
|
if before == "" {
|
|
step("version", fmt.Sprintf("版本号随新代码写入 config.yaml:%s(重启后页头生效)", after))
|
|
return
|
|
}
|
|
step("version", fmt.Sprintf("版本号已随新代码更新:%s → %s(config.yaml,重启后页头生效)", before, after))
|
|
}
|
|
|
|
// restoreVersionAfterRollback puts the old number back with the old code - but only while
|
|
// the live config still carries exactly what the update wrote: a version someone edited by
|
|
// hand after the update belongs to them, not to the rollback.
|
|
func restoreVersionAfterRollback(root string, st State) {
|
|
if st.VersionAfter == "" {
|
|
return
|
|
}
|
|
path := liveConfigFile(root)
|
|
if path == "" {
|
|
return
|
|
}
|
|
current, err := config.FileVersion(path)
|
|
if err != nil || current != st.VersionAfter {
|
|
return
|
|
}
|
|
_, _ = config.WriteVersion(path, st.VersionBefore)
|
|
}
|