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
100 lines
3.7 KiB
Go
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)
|
|
}
|