feat(thothctl): add safe Pi core restart

This commit is contained in:
2026-08-14 17:52:35 +02:00
parent a94df262f4
commit 422f1d47b4
5 changed files with 570 additions and 29 deletions
+12 -7
View File
@@ -863,6 +863,7 @@ type fakeRunner struct {
labelVersion string
activeSessions bool
built bool
recreated bool
currentImage string
mountsJSON string
sessionsWire string
@@ -911,7 +912,7 @@ func (f *fakeRunner) Run(_ context.Context, args []string, _ io.Reader) (compose
f.execFailuresWhileStopped++
return compose.Result{ExitCode: 1}, errors.New("core service is not running")
}
if f.built && f.fail != "compensation" && strings.Contains(call, "image tag sha256:old") {
if (f.built || f.recreated) && f.fail != "compensation" && strings.Contains(call, "image tag sha256:old") {
f.fail = ""
}
if f.fail == "preflight" && strings.Contains(call, "config --format json") {
@@ -926,22 +927,22 @@ func (f *fakeRunner) Run(_ context.Context, args []string, _ io.Reader) (compose
if f.fail == "tag" && len(args) >= 4 && args[0] == "image" && args[1] == "tag" && strings.Contains(args[3], "-candidate") {
return compose.Result{ExitCode: 1}, errors.New("tag token=secret")
}
if f.fail == "health" && f.built && strings.Contains(call, "curl -fsS http://127.0.0.1:8787/health") {
if f.fail == "health" && (f.built || f.recreated) && strings.Contains(call, "curl -fsS http://127.0.0.1:8787/health") {
return compose.Result{ExitCode: 1}, errors.New("health token=secret")
}
if f.fail == "compensation" && f.built && !f.rollbackPrepared && strings.Contains(call, "curl -fsS http://127.0.0.1:8787/health") {
if f.fail == "compensation" && (f.built || f.recreated) && !f.rollbackPrepared && strings.Contains(call, "curl -fsS http://127.0.0.1:8787/health") {
return compose.Result{ExitCode: 1}, errors.New("candidate health failure")
}
if f.fail == "version" && f.built && strings.Contains(call, "pi --version") && strings.Contains(call, "exec") {
if f.fail == "version" && (f.built || f.recreated) && strings.Contains(call, "pi --version") && strings.Contains(call, "exec") {
return compose.Result{ExitCode: 1}, errors.New("version token=secret")
}
if f.fail == "smoke" && f.built && strings.Contains(call, "127.0.0.1:8787/pi-management/test") {
if f.fail == "smoke" && (f.built || f.recreated) && strings.Contains(call, "127.0.0.1:8787/pi-management/test") {
return compose.Result{ExitCode: 1}, errors.New("smoke token=secret")
}
switch {
case strings.Contains(call, "config --format json"):
endpoint := "https://llm.example.invalid"
if f.fail == "config-drift" && f.currentImage == "sha256:candidate" {
if f.fail == "config-drift" && (f.built || f.recreated) {
endpoint = "https://drift.example.invalid"
}
return compose.Result{Stdout: `{"services":{"core":{"image":"` + selectedCoreReference(args, f.configuredImage) + `","environment":{"THT_LLM_URL":"` + endpoint + `"}}}}`}, nil
@@ -953,11 +954,14 @@ func (f *fakeRunner) Run(_ context.Context, args []string, _ io.Reader) (compose
case strings.Contains(call, "ps -q core"):
return compose.Result{Stdout: "core-container\n"}, nil
case strings.Contains(call, "inspect --format {{.Image}}"):
if f.fail == "image-drift" && f.recreated {
return compose.Result{Stdout: "sha256:unexpected\n"}, nil
}
return compose.Result{Stdout: f.currentImage + "\n"}, nil
case strings.Contains(call, "io.thothii.pi.version"):
return compose.Result{Stdout: f.labelVersion + "\n"}, nil
case strings.Contains(call, "inspect --format {{json .Mounts}}"):
if f.fail == "mount-drift" && f.currentImage == "sha256:candidate" {
if f.fail == "mount-drift" && (f.built || f.recreated) {
return compose.Result{Stdout: `[{"Type":"volume","Name":"wrong-settings","Source":"wrong-settings","Destination":"/data/settings","RW":true}]`}, nil
}
if f.mountsJSON != "" {
@@ -1046,6 +1050,7 @@ func (f *fakeRunner) Run(_ context.Context, args []string, _ io.Reader) (compose
}
return compose.Result{}, nil
case containsArg(args, "up"):
f.recreated = true
f.maintenanceAtRecreate = append(f.maintenanceAtRecreate, f.maintenance)
reference := selectedCoreReference(args, f.configuredImage)
if id, ok := f.tags[reference]; ok {