Initial commit: stalwart-migrator design and scaffolding

In-place upgrade tool for Stalwart Mail Server (0.15.5 -> latest) with
checkpointed rollback and post-migration validation. Design stage; see
ARCHITECTURE.md.
This commit is contained in:
2026-08-22 18:17:17 -07:00
commit 719a945d64
71 changed files with 6677 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
package preflight
import (
"fmt"
"io/fs"
"path/filepath"
"syscall"
)
// DirSize walks dir and sums the size of every regular file in it. Used to
// estimate how much free space a filesystem-level backup copy will need -
// see ARCHITECTURE.md §4.2.
func DirSize(dir string) (int64, error) {
var total int64
err := filepath.WalkDir(dir, func(_ string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.Type().IsRegular() {
info, err := d.Info()
if err != nil {
return err
}
total += info.Size()
}
return nil
})
if err != nil {
return 0, fmt.Errorf("preflight: measure size of %s: %w", dir, err)
}
return total, nil
}
// FreeBytes returns the free space available (to an unprivileged process)
// on the filesystem containing path.
func FreeBytes(path string) (uint64, error) {
var stat syscall.Statfs_t
if err := syscall.Statfs(path, &stat); err != nil {
return 0, fmt.Errorf("preflight: statfs %s: %w", path, err)
}
return stat.Bavail * uint64(stat.Bsize), nil
}
func humanBytes(n uint64) string {
const unit = 1024
if n < unit {
return fmt.Sprintf("%d B", n)
}
div, exp := uint64(unit), 0
for n/div >= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %ciB", float64(n)/float64(div), "KMGTPE"[exp])
}