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
+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 {