feat: comprehensive debug logging across all controller modules
Add detailed [DEBUG] logging to every controller module when logging.level is set to "debug". Each module with stateful debug uses SetDebug(bool) wired from main.go. Covers stacks, backup, cloudflare, integrations, system, monitor, settings, scheduler, web handlers, storage, metrics, API, selfupdate, and assets. Also includes the app export/import (.fab bundles) feature from v0.32.0 and its debug page integration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -71,6 +71,10 @@ func ProtectedHDDPaths(hddPath string) map[string]bool {
|
||||
// DeleteStack removes an orphaned stack: stops containers, removes volumes,
|
||||
// optionally removes HDD data, and deletes the stack directory.
|
||||
func (m *Manager) DeleteStack(name string, removeHDDData bool) (*DeleteResponse, error) {
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] DeleteStack called: name=%q, removeHDDData=%v", name, removeHDDData)
|
||||
}
|
||||
|
||||
// Safety: never delete protected stacks
|
||||
if m.cfg.IsProtectedStack(name) {
|
||||
return nil, fmt.Errorf("stack %q is protected and cannot be deleted", name)
|
||||
@@ -81,6 +85,11 @@ func (m *Manager) DeleteStack(name string, removeHDDData bool) (*DeleteResponse,
|
||||
return nil, fmt.Errorf("stack %q not found", name)
|
||||
}
|
||||
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] DeleteStack %s: state=%s, deployed=%v, orphaned=%v, deploying=%v",
|
||||
name, stack.State, stack.Deployed, stack.Orphaned, stack.Deploying)
|
||||
}
|
||||
|
||||
// Must be orphaned
|
||||
if !stack.Orphaned {
|
||||
return nil, fmt.Errorf("stack %q is not orphaned — only orphaned stacks can be deleted", name)
|
||||
@@ -108,11 +117,20 @@ func (m *Manager) DeleteStack(name string, removeHDDData bool) (*DeleteResponse,
|
||||
|
||||
// Step 1: Parse compose file for HDD bind mounts
|
||||
hddMounts := ParseComposeHDDMounts(stack.ComposePath, hddPath)
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] DeleteStack %s: found %d HDD mounts from compose file", name, len(hddMounts))
|
||||
for i, mount := range hddMounts {
|
||||
m.logger.Printf("[DEBUG] [stacks] DeleteStack %s: HDD mount[%d]=%s", name, i, mount)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Run docker compose down --rmi local --volumes
|
||||
// H14: Return error if docker compose down fails — continuing would leave orphaned containers.
|
||||
env := m.stackEnv(stackDir)
|
||||
output, err := m.composeExecCustomEnv(stackDir, env, "down", "--rmi", "local", "--volumes")
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] DeleteStack %s: compose down output: %s", name, truncateStr(output, 500))
|
||||
}
|
||||
if err != nil {
|
||||
m.logger.Printf("[ERROR] docker compose down for %s failed: %v (output: %s)", name, err, truncateStr(output, 200))
|
||||
return resp, fmt.Errorf("docker compose down failed for %s: %w", name, err)
|
||||
@@ -137,12 +155,18 @@ func (m *Manager) DeleteStack(name string, removeHDDData bool) (*DeleteResponse,
|
||||
}
|
||||
|
||||
if _, err := os.Stat(cleanPath); os.IsNotExist(err) {
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] DeleteStack %s: HDD path does not exist, skipping: %s", name, cleanPath)
|
||||
}
|
||||
continue // path doesn't exist, nothing to do
|
||||
}
|
||||
|
||||
if removeHDDData {
|
||||
// Get size before removal
|
||||
sizeHuman := getDirSizeHuman(cleanPath)
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] DeleteStack %s: removing HDD path %s (%s)", name, cleanPath, sizeHuman)
|
||||
}
|
||||
if err := os.RemoveAll(cleanPath); err != nil {
|
||||
m.logger.Printf("[ERROR] Failed to remove HDD data %s: %v", cleanPath, err)
|
||||
} else {
|
||||
@@ -151,11 +175,17 @@ func (m *Manager) DeleteStack(name string, removeHDDData bool) (*DeleteResponse,
|
||||
}
|
||||
} else {
|
||||
sizeHuman := getDirSizeHuman(cleanPath)
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] DeleteStack %s: preserving HDD path %s (%s)", name, cleanPath, sizeHuman)
|
||||
}
|
||||
resp.HDDPathsPreserved = append(resp.HDDPathsPreserved, fmt.Sprintf("%s (%s)", cleanPath, sizeHuman))
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Remove stack directory
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] DeleteStack %s: removing stack directory %s", name, stackDir)
|
||||
}
|
||||
if err := os.RemoveAll(stackDir); err != nil {
|
||||
m.logger.Printf("[ERROR] Failed to remove stack directory %s: %v", stackDir, err)
|
||||
return resp, fmt.Errorf("failed to remove stack directory: %w", err)
|
||||
@@ -188,12 +218,19 @@ func (m *Manager) GetStackHDDData(name string) (*HDDDataResponse, error) {
|
||||
}
|
||||
|
||||
if hddPath == "" {
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] GetStackHDDData %s: no HDD path configured, returning empty", name)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
mounts := ParseComposeHDDMounts(stack.ComposePath, hddPath)
|
||||
protected := ProtectedHDDPaths(hddPath)
|
||||
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] GetStackHDDData %s: found %d raw HDD mounts from compose", name, len(mounts))
|
||||
}
|
||||
|
||||
for _, mount := range mounts {
|
||||
cleanPath := filepath.Clean(mount)
|
||||
|
||||
@@ -221,6 +258,14 @@ func (m *Manager) GetStackHDDData(name string) (*HDDDataResponse, error) {
|
||||
}
|
||||
|
||||
resp.HasHDDData = len(resp.HDDPaths) > 0
|
||||
|
||||
if m.isDebug() {
|
||||
for _, p := range resp.HDDPaths {
|
||||
m.logger.Printf("[DEBUG] [stacks] GetStackHDDData %s: path=%s exists=%v size=%s", name, p.Path, p.Exists, p.SizeHuman)
|
||||
}
|
||||
m.logger.Printf("[DEBUG] [stacks] GetStackHDDData %s: hasHDDData=%v, %d paths returned", name, resp.HasHDDData, len(resp.HDDPaths))
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -229,6 +274,10 @@ func (m *Manager) GetStackHDDData(name string) (*HDDDataResponse, error) {
|
||||
// so the stack reverts to "not deployed" state. The template files (docker-compose.yml,
|
||||
// .felhom.yml) are preserved so the user can redeploy.
|
||||
func (m *Manager) RemoveStack(name string, removeHDDData bool, backupPathsToRemove []string) (*RemoveResponse, error) {
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] RemoveStack called: name=%q, removeHDDData=%v, backupPathsToRemove=%d", name, removeHDDData, len(backupPathsToRemove))
|
||||
}
|
||||
|
||||
// Safety: never remove protected stacks
|
||||
if m.cfg.IsProtectedStack(name) {
|
||||
return nil, fmt.Errorf("stack %q is protected and cannot be removed", name)
|
||||
@@ -239,6 +288,11 @@ func (m *Manager) RemoveStack(name string, removeHDDData bool, backupPathsToRemo
|
||||
return nil, fmt.Errorf("stack %q not found", name)
|
||||
}
|
||||
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] RemoveStack %s: state=%s, deployed=%v, orphaned=%v, deploying=%v",
|
||||
name, stack.State, stack.Deployed, stack.Orphaned, stack.Deploying)
|
||||
}
|
||||
|
||||
// Must be deployed
|
||||
if !stack.Deployed {
|
||||
return nil, fmt.Errorf("stack %q is not deployed", name)
|
||||
@@ -266,10 +320,19 @@ func (m *Manager) RemoveStack(name string, removeHDDData bool, backupPathsToRemo
|
||||
|
||||
// Step 1: Parse compose file for HDD bind mounts
|
||||
hddMounts := ParseComposeHDDMounts(stack.ComposePath, hddPath)
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] RemoveStack %s: found %d HDD mounts from compose file", name, len(hddMounts))
|
||||
for i, mount := range hddMounts {
|
||||
m.logger.Printf("[DEBUG] [stacks] RemoveStack %s: HDD mount[%d]=%s", name, i, mount)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Run docker compose down --volumes (keep images for potential redeploy)
|
||||
env := m.stackEnv(stackDir)
|
||||
output, err := m.composeExecCustomEnv(stackDir, env, "down", "--volumes")
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] RemoveStack %s: compose down output: %s", name, truncateStr(output, 500))
|
||||
}
|
||||
if err != nil {
|
||||
m.logger.Printf("[ERROR] docker compose down for %s failed: %v (output: %s)", name, err, truncateStr(output, 200))
|
||||
return resp, fmt.Errorf("docker compose down failed for %s: %w", name, err)
|
||||
@@ -293,11 +356,17 @@ func (m *Manager) RemoveStack(name string, removeHDDData bool, backupPathsToRemo
|
||||
}
|
||||
|
||||
if _, err := os.Stat(cleanPath); os.IsNotExist(err) {
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] RemoveStack %s: HDD path does not exist, skipping: %s", name, cleanPath)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if removeHDDData {
|
||||
sizeHuman := getDirSizeHuman(cleanPath)
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] RemoveStack %s: removing HDD path %s (%s)", name, cleanPath, sizeHuman)
|
||||
}
|
||||
if err := os.RemoveAll(cleanPath); err != nil {
|
||||
m.logger.Printf("[ERROR] Failed to remove HDD data %s: %v", cleanPath, err)
|
||||
} else {
|
||||
@@ -306,12 +375,18 @@ func (m *Manager) RemoveStack(name string, removeHDDData bool, backupPathsToRemo
|
||||
}
|
||||
} else {
|
||||
sizeHuman := getDirSizeHuman(cleanPath)
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] RemoveStack %s: preserving HDD path %s (%s)", name, cleanPath, sizeHuman)
|
||||
}
|
||||
resp.HDDPathsPreserved = append(resp.HDDPathsPreserved, fmt.Sprintf("%s (%s)", cleanPath, sizeHuman))
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Handle backup data cleanup
|
||||
backupsBase := filepath.Join(hddPath, felhomDataDir, "backups")
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] RemoveStack %s: processing %d backup paths for removal (base=%s)", name, len(backupPathsToRemove), backupsBase)
|
||||
}
|
||||
for _, bkPath := range backupPathsToRemove {
|
||||
cleanPath := filepath.Clean(bkPath)
|
||||
// Validate path is under the expected backups directory
|
||||
@@ -333,6 +408,9 @@ func (m *Manager) RemoveStack(name string, removeHDDData bool, backupPathsToRemo
|
||||
|
||||
// Step 6: Remove app.yaml only (keep template files for redeploy)
|
||||
appYAMLPath := filepath.Join(stackDir, "app.yaml")
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] RemoveStack %s: removing app.yaml at %s", name, appYAMLPath)
|
||||
}
|
||||
if err := os.Remove(appYAMLPath); err != nil && !os.IsNotExist(err) {
|
||||
m.logger.Printf("[ERROR] Failed to remove %s: %v", appYAMLPath, err)
|
||||
return resp, fmt.Errorf("failed to remove app.yaml: %w", err)
|
||||
@@ -368,6 +446,9 @@ func (m *Manager) GetStackBackupData(name string, drivePath string) (*BackupData
|
||||
}
|
||||
|
||||
if drivePath == "" {
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] GetStackBackupData %s: no drive path provided, returning empty", name)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -379,6 +460,12 @@ func (m *Manager) GetStackBackupData(name string, drivePath string) (*BackupData
|
||||
rsyncPath := filepath.Join(drivePath, felhomDataDir, "backups", "secondary", name, "rsync")
|
||||
resp.BackupPaths = append(resp.BackupPaths, buildPathInfo(rsyncPath))
|
||||
|
||||
if m.isDebug() {
|
||||
for _, p := range resp.BackupPaths {
|
||||
m.logger.Printf("[DEBUG] [stacks] GetStackBackupData %s: checked path=%s exists=%v size=%s", name, p.Path, p.Exists, p.SizeHuman)
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range resp.BackupPaths {
|
||||
if p.Exists {
|
||||
resp.HasBackups = true
|
||||
@@ -386,6 +473,10 @@ func (m *Manager) GetStackBackupData(name string, drivePath string) (*BackupData
|
||||
}
|
||||
}
|
||||
|
||||
if m.isDebug() {
|
||||
m.logger.Printf("[DEBUG] [stacks] GetStackBackupData %s: hasBackups=%v", name, resp.HasBackups)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user