Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

100 lines
3.7 KiB
Go

package platform
import (
"os"
"path/filepath"
"strings"
)
// MigrateToUnifiedHome relocates per-user state written by older Gortex
// versions (which split files across ~/.config/gortex, ~/.cache/gortex,
// and a flat ~/.gortex) into the unified ~/.gortex tree this package now
// resolves. It is best-effort and idempotent: a destination that already
// exists is never overwritten, and individual move failures are reported
// to logf but never abort the caller.
//
// It is a no-op when any XDG_*_HOME variable is set to an absolute path —
// that signals the user opted into the XDG layout, so there is nothing to
// unify. logf may be nil. Because every step short-circuits once its
// destination exists, the function is cheap to call on every startup; it
// only logs (and only does work) on the first run after the upgrade.
func MigrateToUnifiedHome(logf func(format string, args ...any)) {
if logf == nil {
logf = func(string, ...any) {}
}
// Respect an explicit XDG opt-in: relocate nothing.
for _, v := range []string{"XDG_CONFIG_HOME", "XDG_DATA_HOME", "XDG_CACHE_HOME"} {
if val := os.Getenv(v); val != "" && filepath.IsAbs(val) {
return
}
}
home, err := os.UserHomeDir()
if err != nil || home == "" {
return
}
root := filepath.Join(home, homeDir) // ~/.gortex
// 1. Global config moves out of ~/.config/gortex into the root.
migrateInto(logf, filepath.Join(home, ".config", gortexDir, "config.yaml"), filepath.Join(root, "config.yaml"))
migrateInto(logf, filepath.Join(home, ".config", gortexDir, "servers.toml"), filepath.Join(root, "servers.toml"))
// 2. The old ~/.cache/gortex tree folds into ~/.gortex/cache, except
// downloaded models (durable data, kept out of cache so a cache
// wipe doesn't discard them) and the stale daemon socket / pid
// (regenerated on the next start).
oldCache := filepath.Join(home, ".cache", gortexDir)
if entries, err := os.ReadDir(oldCache); err == nil {
for _, e := range entries {
switch e.Name() {
case "daemon.sock", "daemon.pid":
continue
case "models":
migrateInto(logf, filepath.Join(oldCache, "models"), filepath.Join(root, "models"))
default:
migrateInto(logf, filepath.Join(oldCache, e.Name()), filepath.Join(root, cacheSub, e.Name()))
}
}
}
// 3. In-place reorg of the ~/.gortex root: the backend store (and its
// WAL/shm sidecars) move under store/, and the old memories-cache
// directory becomes memories/.
if entries, err := os.ReadDir(root); err == nil {
for _, e := range entries {
name := e.Name()
if e.IsDir() {
continue
}
if strings.HasSuffix(name, ".store") || strings.HasPrefix(name, "store.sqlite") {
migrateInto(logf, filepath.Join(root, name), filepath.Join(root, "store", name))
}
}
}
migrateInto(logf, filepath.Join(root, "memories-cache"), filepath.Join(root, "memories"))
}
// migrateInto moves src to dst when src exists and dst does not. The move
// is a rename (atomic within a filesystem); a cross-device failure is
// logged and the source left in place rather than risking a partial copy
// of a live store. Idempotent: a pre-existing dst short-circuits.
func migrateInto(logf func(string, ...any), src, dst string) {
if src == dst {
return
}
if _, err := os.Lstat(src); err != nil {
return // nothing to migrate
}
if _, err := os.Lstat(dst); err == nil {
return // already present — never clobber
}
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
logf("gortex: migrate %s: mkdir parent failed: %v", dst, err)
return
}
if err := os.Rename(src, dst); err != nil {
logf("gortex: could not migrate %s -> %s (move it manually): %v", src, dst, err)
return
}
logf("gortex: migrated %s -> %s", src, dst)
}