diff --git a/cmd/mbf/main.go b/cmd/mbf/main.go index 471b595a..a6c74a0f 100644 --- a/cmd/mbf/main.go +++ b/cmd/mbf/main.go @@ -568,6 +568,47 @@ func main() { }, ) + c.NewCommand( + "snapshot", + "Populate an empty directory with a static mirror snapshot", + func(args []string) error { + if len(args) != 1 { + return errors.New("snapshot requires 1 argument") + } + + var key ed25519.PrivateKey + if p, err := os.ReadFile(filepath.Join(cm.base, "ed25519")); err != nil { + return err + } else if len(p) != ed25519.PrivateKeySize { + return errors.New("invalid private key") + } else { + key = p + } + + var m rosa.Mirror + if base, err := os.OpenRoot(cm.base); err != nil { + return err + } else { + m = rosa.NewMirror(msg, base.FS(), key) + } + + retry: + root, err := os.OpenRoot(args[0]) + if err != nil { + if os.IsNotExist(err) { + if err = os.MkdirAll(args[0], 0755); err != nil { + return err + } + goto retry + } + return err + } + + go func() { <-ctx.Done(); _ = root.Close() }() + return m.Populate(root) + }, + ) + { var ( flagGentoo string diff --git a/internal/rosa/mirror.go b/internal/rosa/mirror.go index d4dfba1e..80d4509c 100644 --- a/internal/rosa/mirror.go +++ b/internal/rosa/mirror.go @@ -14,6 +14,7 @@ import ( "path/filepath" "strconv" "strings" + "syscall" "unique" "hakurei.app/internal/pkg" @@ -211,6 +212,114 @@ type Mirror struct { msg message.Msg } +// Populate fills an empty root with a static mirror snapshot. +func (m Mirror) Populate(root *os.Root) error { + if dents, err := fs.ReadDir(root.FS(), "."); err != nil { + return err + } else if len(dents) > 0 { + return syscall.ENOTEMPTY + } + + for _, name := range []string{ + dirArtifact, + dirOutcome, + dirStatus, + } { + if err := root.Mkdir(name, 0755); err != nil { + return err + } + } + var w *os.File + + if dents, err := fs.ReadDir(m.fsys, "identifier"); err != nil { + return err + } else { + for _, dent := range dents { + name := dent.Name() + var buf [2 * len(pkg.Checksum{})]byte + if err = pkg.Decode( + (*pkg.Checksum)(buf[:len(pkg.Checksum{})]), + name, + ); err != nil { + return err + } + + if w, err = root.OpenFile( + filepath.Join(dirArtifact, name), + syscall.O_WRONLY|syscall.O_CREAT|syscall.O_EXCL, + 0444, + ); err != nil { + return err + } + err = m.writeArtifact(&buf, w, nil) + if err = errors.Join(err, w.Close()); err != nil { + return err + } + } + } + + if dents, err := fs.ReadDir(m.fsys, "checksum"); err != nil { + return err + } else { + for _, dent := range dents { + name := dent.Name() + var buf pkg.Checksum + if err = pkg.Decode(&buf, name); err != nil { + return err + } + + var fsys fs.FS + fsys, err = fs.Sub(m.fsys, filepath.Join("checksum", name)) + if err != nil { + return err + } + + if w, err = root.OpenFile( + filepath.Join(dirOutcome, name), + syscall.O_WRONLY|syscall.O_CREAT|syscall.O_EXCL, + 0444, + ); err != nil { + return err + } + + m.msg.Verbosef("exporting outcome %s", name) + err = pkg.Write(fsys, ".", w) + if err = errors.Join(err, w.Close()); err != nil { + return err + } + } + } + + if dents, err := fs.ReadDir(m.fsys, "status"); err != nil { + return err + } else { + for _, dent := range dents { + name := dent.Name() + var buf [2 * len(pkg.Checksum{})]byte + if err = pkg.Decode( + (*pkg.Checksum)(buf[:len(pkg.Checksum{})]), + name, + ); err != nil { + return err + } + + if w, err = root.OpenFile( + filepath.Join(dirStatus, name), + syscall.O_WRONLY|syscall.O_CREAT|syscall.O_EXCL, + 0444, + ); err != nil { + return err + } + err = m.writeStatus(&buf, w, nil) + if err = errors.Join(err, w.Close()); err != nil { + return err + } + } + } + + return nil +} + // NewMirror returns a populated [Mirror] for servicing mirror requests. func NewMirror( msg message.Msg,