Files
llmgw/internal/cli/backupcopy.go
T

85 lines
2.5 KiB
Go

package cli
import (
"context"
"fmt"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"gitea.mynodes.xyz/ilker/llmgw/internal/onboard"
)
// copyBackup puts an archive where server.yaml's backup_copy_to says, and
// keeps as many there as here. It says each copy, and fails on the first
// that cannot be made.
func copyBackup(ctx context.Context, archive string, targets []string, key, knownHosts string, keep int, say func(string)) error {
for _, t := range targets {
if strings.HasPrefix(t, "ssh://") {
if err := copyOverSSH(ctx, archive, t, key, knownHosts, keep); err != nil {
return fmt.Errorf("%s: %w", t, err)
}
} else {
if err := copyToDir(archive, t, keep); err != nil {
return fmt.Errorf("%s: %w", t, err)
}
}
say("copied to " + t)
}
return nil
}
func copyToDir(archive, dir string, keep int) error {
if err := os.MkdirAll(dir, 0o700); err != nil {
return err
}
b, err := os.ReadFile(archive) //nolint:gosec // the archive just written
if err != nil {
return err
}
final := filepath.Join(dir, filepath.Base(archive))
if err := os.WriteFile(final+".part", b, 0o600); err != nil {
return err
}
if err := os.Rename(final+".part", final); err != nil {
return err
}
_, err = pruneBackups(dir, keep)
return err
}
func copyOverSSH(ctx context.Context, archive, target, key, knownHosts string, keep int) error {
u, err := url.Parse(target)
if err != nil || u.User == nil || u.Host == "" || u.Path == "" {
return fmt.Errorf("not ssh://user@host/path")
}
port := 22
if p := u.Port(); p != "" {
port, _ = strconv.Atoi(p)
}
b, err := os.ReadFile(archive) //nolint:gosec // the archive just written
if err != nil {
return err
}
c := &onboard.GoSSH{Addr: u.Hostname(), Port: port, User: u.User.Username(), KeyFile: key, KnownHosts: knownHosts}
defer c.Close()
dir, name := shellQuote(u.Path), shellQuote(filepath.Base(archive))
// Written beside its name and renamed, then the oldest beyond keep
// removed: only archives this command wrote, by their names.
script := fmt.Sprintf(`set -e
umask 077
mkdir -p %[1]s
cat > %[1]s/%[2]s.part
mv %[1]s/%[2]s.part %[1]s/%[2]s
ls -1t %[1]s | grep -E '^llmgw-backup-.*\.tar\.gz(\.age)?$' | tail -n +%[3]d | while read -r f; do rm -f %[1]s/"$f"; done
`, dir, name, keep+1)
if out, err := c.Run(ctx, script, b); err != nil {
return fmt.Errorf("%w: %s", err, strings.TrimSpace(string(out)))
}
return nil
}
func shellQuote(s string) string { return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'" }