fix: deep bug hunt II — concurrency, security & optimization (25 files)
Critical: watchdog mutex panic safety, SetGeoAppOverride nil guard, SSD-only app DB restore fallback. High: double deploy race (atomic Deploying flag), delete/remove during deploy guard, ScanStacks overwrite protection, FileBrowser mount mutex, PushEvent history, PushOnce error handling, DB dump sync+close before rename, restic retry fresh context, encrypt failure logging, cross-backup path traversal validation, deepCopyStack completeness. Security: constant-time API key comparison, login rate limiting (5/min), git credential masking in logs, storage path prefix traversal fix. Concurrency: MigrateEncryption lock ordering, SubdomainInUse I/O outside lock, scheduler late-registered jobs, SQLite WAL verification, metrics shutdown context, telemetry scan error logging, asset sync lock scope. Optimization: streaming file copy for DB dumps, restic stats dedup, atomic infra config copy. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ package backup
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -475,12 +476,8 @@ func (r *CrossDriveRunner) copyStackDBDumps(stackName, destDir string) error {
|
||||
}
|
||||
src := filepath.Join(dumpDir, e.Name())
|
||||
dst := filepath.Join(destDir, e.Name())
|
||||
data, err := os.ReadFile(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading %s: %w", e.Name(), err)
|
||||
}
|
||||
if err := os.WriteFile(dst, data, 0644); err != nil {
|
||||
return fmt.Errorf("writing %s: %w", e.Name(), err)
|
||||
if err := copyFile(src, dst); err != nil {
|
||||
return fmt.Errorf("copying %s: %w", e.Name(), err)
|
||||
}
|
||||
copied++
|
||||
}
|
||||
@@ -523,14 +520,11 @@ func (r *CrossDriveRunner) syncInfraConfig(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// Copy controller.yaml → _infra/controller.yaml
|
||||
// Copy controller.yaml → _infra/controller.yaml (atomic via copyFile)
|
||||
if _, err := os.Stat(r.controllerYAMLPath); err == nil {
|
||||
yamlDest := filepath.Join(infraDir, "controller.yaml")
|
||||
data, err := os.ReadFile(r.controllerYAMLPath)
|
||||
if err != nil {
|
||||
r.logger.Printf("[WARN] Cannot read controller.yaml for infra backup: %v", err)
|
||||
} else if err := os.WriteFile(yamlDest, data, 0644); err != nil {
|
||||
r.logger.Printf("[WARN] Cannot write controller.yaml to %s: %v", yamlDest, err)
|
||||
if err := copyFile(r.controllerYAMLPath, yamlDest); err != nil {
|
||||
r.logger.Printf("[WARN] Cannot copy controller.yaml to %s: %v", yamlDest, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,6 +622,32 @@ func (r *CrossDriveRunner) updateStatus(stackName, status, errMsg string, durati
|
||||
})
|
||||
}
|
||||
|
||||
// copyFile copies src to dst using buffered streaming I/O (no full-file memory allocation).
|
||||
func copyFile(src, dst string) error {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
tmp := dst + ".tmp"
|
||||
out, err := os.Create(tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(out, in); err != nil {
|
||||
out.Close()
|
||||
os.Remove(tmp)
|
||||
return err
|
||||
}
|
||||
if err := out.Close(); err != nil {
|
||||
os.Remove(tmp)
|
||||
return err
|
||||
}
|
||||
return os.Rename(tmp, dst)
|
||||
}
|
||||
|
||||
// dirSizeBytes returns the total byte size of all files under path.
|
||||
// H7: Walk errors are now propagated instead of silently swallowed.
|
||||
func dirSizeBytes(path string) (int64, error) {
|
||||
|
||||
Reference in New Issue
Block a user