fix(ops): make server diagnostics release-safe

This commit is contained in:
User
2026-09-07 01:15:28 +02:00
parent cffa60772e
commit 28db30bd78
10 changed files with 297 additions and 38 deletions
@@ -14,6 +14,7 @@ import (
func TestRestoreAuthBearingArchiveRefusesNonRootBeforeTransactionOrWrite(t *testing.T) {
installation, archive := projectedRestoreFixture(t)
deps := restoreTestDependencies(t, newBackupRunner(installation, false))
deps.requireAuthProjection = requireAuthProjectionRestorePrivilege
checkpointCalled := false
beginCalled := false
writeCalled := false
+79 -9
View File
@@ -373,27 +373,97 @@ func filePermissions(installation config.Installation) error {
return nil
}
// ValidateVolumes checks the eight persistent volumes required by a local ThothII installation.
type renderedMount struct {
Type string `json:"type"`
Target string `json:"target"`
ReadOnly bool `json:"read_only"`
}
type renderedService struct {
Volumes []renderedMount `json:"volumes"`
}
type renderedPersistence struct {
Volumes map[string]json.RawMessage `json:"volumes"`
Services map[string]renderedService `json:"services"`
}
var requiredNamedVolumes = []string{
"settings", "pi-state", "workspace-registry", "workspace-secrets",
"sessions", "qdrant-data", "embedding-models", "auth-state",
}
var requiredPersistentMounts = []struct {
service string
target string
label string
}{
{service: "core", target: "/data/settings", label: "settings"},
{service: "core", target: "/home/thoth/.pi", label: "pi-state"},
{service: "core", target: "/data/workspace-registry", label: "workspace-registry"},
{service: "core", target: "/data/workspace-secrets", label: "workspace-secrets"},
{service: "core", target: "/data/sessions", label: "sessions"},
{service: "qdrant", target: "/qdrant/storage", label: "qdrant-data"},
{service: "embedding", target: "/root/.ollama", label: "embedding-models"},
{service: "core", target: "/data/auth", label: "auth-state"},
}
// ValidateVolumes accepts either the portable named-volume layout or the server layout where a
// writable bind root owns several nested persistence paths. Docker Compose omits unused top-level
// volume declarations after a server override, so declarations alone cannot validate that profile.
func ValidateVolumes(rendered string) error {
var document struct {
Volumes map[string]json.RawMessage `json:"volumes"`
}
var document renderedPersistence
if err := json.Unmarshal([]byte(rendered), &document); err != nil {
return errors.New("Compose returned invalid rendered configuration")
}
for _, name := range []string{"settings", "pi-state", "workspace-registry", "workspace-secrets", "sessions", "qdrant-data", "embedding-models", "auth-state"} {
missing := ""
for _, name := range requiredNamedVolumes {
if _, exists := document.Volumes[name]; !exists {
return fmt.Errorf("rendered Compose configuration is missing required volume %s", name)
missing = name
break
}
}
if missing == "" {
return nil
}
if len(document.Services) == 0 {
return fmt.Errorf("rendered Compose configuration is missing required volume %s", missing)
}
for _, required := range requiredPersistentMounts {
service, exists := document.Services[required.service]
if !exists || !hasWritableMountCovering(service.Volumes, required.target) {
return fmt.Errorf("rendered Compose configuration is missing persistent mount %s", required.label)
}
}
return nil
}
func workspaceRegistryDeclared(rendered string) bool {
var document struct {
Volumes map[string]json.RawMessage `json:"volumes"`
var document renderedPersistence
if json.Unmarshal([]byte(rendered), &document) != nil {
return false
}
return json.Unmarshal([]byte(rendered), &document) == nil && document.Volumes["workspace-registry"] != nil
if document.Volumes["workspace-registry"] != nil {
return true
}
return hasWritableMountCovering(document.Services["core"].Volumes, "/data/workspace-registry")
}
func hasWritableMountCovering(mounts []renderedMount, target string) bool {
for _, mount := range mounts {
if mount.ReadOnly || (mount.Type != "bind" && mount.Type != "volume") {
continue
}
mountTarget := filepath.Clean(mount.Target)
if !filepath.IsAbs(mountTarget) {
continue
}
relative, err := filepath.Rel(mountTarget, target)
if err == nil && relative != ".." && !strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
return true
}
}
return false
}
func commandDetail(label string, result compose.Result, err error, secrets []string) string {
+54
View File
@@ -89,6 +89,38 @@ func TestValidateVolumesRequiresAuthState(t *testing.T) {
}
}
// Catches rejecting a server installation whose durable state is provided by bind mounts.
func TestRunAcceptsServerBindMountPersistence(t *testing.T) {
installation := doctorInstallation(t, "")
installation.Profile = "server"
runner := &doctorRunner{services: healthyServices, rendered: serverRenderedConfig}
report, err := Run(context.Background(), installation, runner)
if err != nil {
t.Fatal(err)
}
if !report.OK {
t.Fatalf("Run() report = %#v, want healthy server bind-mount installation", report)
}
if checkStatus(report, "configuration") != StatusPassed ||
checkStatus(report, "authentication") != StatusPassed ||
checkStatus(report, "workspace-registry") != StatusPassed {
t.Fatalf("Run() report = %#v, want server configuration and dependent checks passed", report)
}
}
func TestValidateVolumesRejectsIncompleteServerBindMountPersistence(t *testing.T) {
withoutPiState := strings.Replace(
serverRenderedConfig,
`{"type": "bind", "source": "/srv/thothii/pi-state", "target": "/home/thoth/.pi"}`,
`{"type": "bind", "source": "/srv/thothii/pi-state", "target": "/tmp/pi-state"}`,
1,
)
if err := ValidateVolumes(withoutPiState); err == nil || !strings.Contains(err.Error(), "pi-state") {
t.Fatalf("ValidateVolumes() error = %v, want missing pi-state persistence", err)
}
}
// Catches Docker availability short-circuiting a host file-permission failure.
func TestRunChecksUnsafeFilesEvenWhenDockerIsUnavailable(t *testing.T) {
installation := doctorInstallation(t, "")
@@ -302,6 +334,7 @@ type doctorRunner struct {
calls []string
dockerUnavailable bool
services string
rendered string
workflowFailure string
registryInvalid bool
}
@@ -320,6 +353,9 @@ func (r *doctorRunner) Run(_ context.Context, args []string, _ io.Reader) (compo
case strings.Contains(call, "config --quiet"):
return compose.Result{}, nil
case strings.Contains(call, "config --format json"):
if r.rendered != "" {
return compose.Result{Stdout: r.rendered}, nil
}
return compose.Result{Stdout: renderedConfig}, nil
case strings.Contains(call, "ps --all --format json"):
if r.services == "" {
@@ -395,6 +431,24 @@ func assertChecklist(t *testing.T, report Report, want []string) {
const renderedConfig = `{"volumes":{"settings":{},"pi-state":{},"workspace-registry":{},"workspace-secrets":{},"sessions":{},"qdrant-data":{},"embedding-models":{},"auth-state":{}},"services":{"core":{"image":"thothii-core:local","environment":{"THT_LLM_URL":"https://llm.example.invalid"}}}}`
const serverRenderedConfig = `{
"volumes": {"catalog-data": {}, "qdrant-data": {}, "embedding-models": {}},
"services": {
"core": {
"image": "thothii-core:server",
"environment": {"THT_LLM_URL": "https://llm.example.invalid"},
"volumes": [
{"type": "bind", "source": "/srv/thothii/data", "target": "/data"},
{"type": "bind", "source": "/srv/thothii/pi-state", "target": "/home/thoth/.pi"},
{"type": "bind", "source": "/srv/thothii/workspace-registry", "target": "/data/workspace-registry"}
]
},
"catalog-db": {"volumes": [{"type": "volume", "source": "catalog-data", "target": "/var/lib/postgresql/data"}]},
"qdrant": {"volumes": [{"type": "volume", "source": "qdrant-data", "target": "/qdrant/storage"}]},
"embedding": {"volumes": [{"type": "volume", "source": "embedding-models", "target": "/root/.ollama"}]}
}
}`
const healthyServices = `[
{"Service":"core","State":"running","Health":"healthy"},
{"Service":"frontend","State":"running","Health":"healthy"},