mirror of
https://github.com/hanzoai/base.git
synced 2026-10-21 03:41:50 +00:00
A backup checkpoints one Base's write-ahead log inside one transaction. A tenant's Base is separate handles with its own log, in neither, so its file was being copied mid-write beside a log copied at another instant — a torn copy rather than a smaller one. The archive names what it leaves out, in the zip's own comment, where every listing tool already prints it. Restore spares exactly what the archive omits. That symmetry is the whole thing: an archive that skips the tenants and a restore that does not spare them deletes every tenant. Both are stated in the package that owns the directory, so core stays ignorant of tenancy. The rate limiter refuses a request it cannot key. A forwarded request arrives with no client address, and a limiter that admits what it cannot count is not one. The way to be counted already exists: a gateway that sets a forwarded header restores the client, through the trusted-proxy settings. Opening a Base holds the registry's lock over the map alone. It had spanned provision, construct, bootstrap and declare, and a read waits behind a waiting writer, so one org's first request stalled every other request in the process — measured at 209ms against a 101ms open. Co-authored-by: Hanzo Dev <[email protected]>
102 lines
2.3 KiB
Go
102 lines
2.3 KiB
Go
package archive
|
|
|
|
import (
|
|
"archive/zip"
|
|
"compress/flate"
|
|
"errors"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// Create creates a new zip archive from src dir content and saves it in dest path.
|
|
//
|
|
// You can specify skipPaths to skip/ignore certain directories and files (relative to src)
|
|
// preventing adding them in the final archive.
|
|
//
|
|
// What was skipped is written as the archive's comment, because listing a zip
|
|
// shows what is in it and nothing shows what is not. Whoever holds the file can
|
|
// then tell what it covers without knowing how it was made.
|
|
func Create(src string, dest string, skipPaths ...string) error {
|
|
if err := os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil {
|
|
return err
|
|
}
|
|
|
|
zf, err := os.Create(dest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
zw := zip.NewWriter(zf)
|
|
|
|
// register a custom Deflate compressor
|
|
zw.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
|
|
return flate.NewWriter(out, flate.BestSpeed)
|
|
})
|
|
|
|
if len(skipPaths) > 0 {
|
|
if err := zw.SetComment("omits " + strings.Join(skipPaths, ", ")); err != nil {
|
|
return errors.Join(err, zw.Close(), zf.Close(), os.Remove(dest))
|
|
}
|
|
}
|
|
|
|
err = zipAddFS(zw, os.DirFS(src), skipPaths...)
|
|
if err != nil {
|
|
// try to cleanup at least the created zip file
|
|
return errors.Join(err, zw.Close(), zf.Close(), os.Remove(dest))
|
|
}
|
|
|
|
return errors.Join(zw.Close(), zf.Close())
|
|
}
|
|
|
|
// note remove after similar method is added in the std lib (https://github.com/golang/go/issues/54898)
|
|
func zipAddFS(w *zip.Writer, fsys fs.FS, skipPaths ...string) error {
|
|
return fs.WalkDir(fsys, ".", func(name string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if d.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
// skip
|
|
for _, ignore := range skipPaths {
|
|
if ignore == name ||
|
|
strings.HasPrefix(filepath.Clean(name)+string(os.PathSeparator), filepath.Clean(ignore)+string(os.PathSeparator)) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
info, err := d.Info()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
h, err := zip.FileInfoHeader(info)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
h.Name = name
|
|
h.Method = zip.Deflate
|
|
|
|
fw, err := w.CreateHeader(h)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
f, err := fsys.Open(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
_, err = io.Copy(fw, f)
|
|
|
|
return err
|
|
})
|
|
}
|