From a0bd7d865087b75519403bf9ff0b84de7aa028fa Mon Sep 17 00:00:00 2001 From: Catherine Date: Wed, 17 Sep 2025 13:13:58 +0000 Subject: [PATCH] Implement migration from v1 data layout. --- src/main.go | 16 ++++++++ src/manifest.go | 2 +- src/migrate.go | 97 +++++++++++++++++++++++++++++++++++++++++++++++++ src/update.go | 2 +- 4 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 src/migrate.go diff --git a/src/main.go b/src/main.go index 36eedb5..d8cb00b 100644 --- a/src/main.go +++ b/src/main.go @@ -5,6 +5,7 @@ import ( "log" "net" "net/http" + "os" ) var backend Backend @@ -26,6 +27,7 @@ func main() { var err error configPath := flag.String("config", "config.toml", "path to configuration file") + migrateV1Path := flag.String("migrate-v1", "", "path to v1 data directory to upload") flag.Parse() if err := ReadConfig(*configPath); err != nil { @@ -55,6 +57,20 @@ func main() { log.Fatalln("unknown backend:", config.Backend.Type) } + if *migrateV1Path != "" { + root, err := os.OpenRoot(*migrateV1Path) + if err != nil { + log.Fatalln("migrate v1:", err) + } + + err = MigrateFromV1(root) + if err != nil { + log.Fatalln("migrate v1:", err) + } + + log.Println("migrate v1 ok") + } + log.Println("ready") if config.Caddy != (ListenConfig{}) { diff --git a/src/manifest.go b/src/manifest.go index c0bf482..508c12b 100644 --- a/src/manifest.go +++ b/src/manifest.go @@ -103,7 +103,7 @@ const ManifestSizeMax int = 1048576 // Accepts a manifest with inline files, returns a manifest with external files after writing // file contents and the manifest itself to the storage. -func StoreManifest(backend Backend, name string, manifest *Manifest) (*Manifest, error) { +func StoreManifest(name string, manifest *Manifest) (*Manifest, error) { extManifest := ExternalizeFiles(manifest) extManifestData := EncodeManifest(extManifest) if len(extManifestData) > ManifestSizeMax { diff --git a/src/migrate.go b/src/migrate.go new file mode 100644 index 0000000..ab0bdbb --- /dev/null +++ b/src/migrate.go @@ -0,0 +1,97 @@ +package main + +import ( + "fmt" + "io/fs" + "log" + "os" + "path/filepath" +) + +func readToManifest(root *os.Root) (*Manifest, error) { + manifest := Manifest{} + manifest.Tree = make(map[string]*Entry) + err := fs.WalkDir(root.FS(), ".", func(path string, dirEntry fs.DirEntry, err error) error { + if err != nil { + return err + } + + manifestEntry := Entry{} + if dirEntry.IsDir() { + manifestEntry.Type = Type_Directory + } else if dirEntry.Type().IsRegular() { + data, err := root.ReadFile(path) + if err != nil { + return err + } + manifestEntry.Type = Type_InlineFile + manifestEntry.Size = int64(len(data)) + manifestEntry.Data = data + } else if dirEntry.Type().Type() == fs.ModeSymlink { + target, err := root.Readlink(path) + if err != nil { + return err + } + manifestEntry.Type = Type_Symlink + manifestEntry.Size = int64(len(target)) + manifestEntry.Data = []byte(target) + } else { + log.Println("migrate v1: illegal %s/%s", root.Name(), path) + } + if path == "." { + path = "" + } + manifest.Tree[path] = &manifestEntry + return nil + }) + return &manifest, err +} + +type ReadDirLinkFS interface { // aaaaahh!!! Why is Go like this!! + fs.ReadDirFS + fs.ReadLinkFS +} + +func MigrateFromV1(root *os.Root) error { + data := root.FS().(ReadDirLinkFS) + + domainDirEntries, err := data.ReadDir("www") + if err != nil { + return err + } + + for _, domainDirEntry := range domainDirEntries { + domain := domainDirEntry.Name() + if !domainDirEntry.IsDir() { + return fmt.Errorf("migrate v1: www/%s: not a directory", domain) + } + + projectDirEntries, err := data.ReadDir(filepath.Join("www", domain)) + if err != nil { + return err + } + + for _, projectDirEntry := range projectDirEntries { + projectName := projectDirEntry.Name() + if projectDirEntry.Type().Type() != fs.ModeSymlink { + return fmt.Errorf("migrate v1: www/%s/%s: not a symlink", domain, projectName) + } + + treeRoot, err := root.OpenRoot(filepath.Join("www", domain, projectName)) + if err != nil { + return err + } + + manifest, err := readToManifest(treeRoot) + if err != nil { + return fmt.Errorf("migrate v1: read %s/%s: %w", domain, projectName, err) + } + + _, err = StoreManifest(fmt.Sprintf("%s/%s", domain, projectName), manifest) + if err != nil { + return fmt.Errorf("migrate v1: store %s/%s: %w", domain, projectName, err) + } + } + } + return nil +} diff --git a/src/update.go b/src/update.go index 0b38cab..e6dc69f 100644 --- a/src/update.go +++ b/src/update.go @@ -41,7 +41,7 @@ func Update( err = fmt.Errorf("update timeout") } else if err == nil { oldManifest, _ = backend.GetManifest(webRoot) - newManifest, err = StoreManifest(backend, webRoot, fetchManifest) + newManifest, err = StoreManifest(webRoot, fetchManifest) if err == nil { if oldManifest == nil { outcome = UpdateCreated