summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--stdlib/backups/borg/common.go9
-rw-r--r--stdlib/backups/borg/server.go100
2 files changed, 109 insertions, 0 deletions
diff --git a/stdlib/backups/borg/common.go b/stdlib/backups/borg/common.go
new file mode 100644
index 0000000..60d14f2
--- /dev/null
+++ b/stdlib/backups/borg/common.go
@@ -0,0 +1,9 @@
+package borg
+
+import gonf "git.adyxax.org/adyxax/gonf/v2/pkg"
+
+func installBorgPackage() gonf.Status {
+ packag := gonf.Package("borgbackup")
+ packag.Resolve()
+ return packag.Status()
+}
diff --git a/stdlib/backups/borg/server.go b/stdlib/backups/borg/server.go
new file mode 100644
index 0000000..95768da
--- /dev/null
+++ b/stdlib/backups/borg/server.go
@@ -0,0 +1,100 @@
+package borg
+
+import (
+ "log/slog"
+ "path/filepath"
+
+ gonf "git.adyxax.org/adyxax/gonf/v2/pkg"
+)
+
+type BorgServer struct {
+ chain []gonf.Promise
+ clients map[string][]byte // name -> publicKey
+ path string
+ user string
+ status gonf.Status
+}
+
+func (b *BorgServer) IfRepaired(p ...gonf.Promise) gonf.Promise {
+ b.chain = append(b.chain, p...)
+ return b
+}
+
+func (b *BorgServer) Promise() gonf.Promise {
+ gonf.MakeCustomPromise(b).Promise()
+ return b
+}
+
+func (b *BorgServer) Resolve() {
+ b.status = gonf.KEPT
+ // Borg user
+ user := gonf.User(gonf.UserData{
+ HomeDir: b.path,
+ Name: "borg",
+ System: true,
+ })
+ user.Resolve()
+ switch user.Status() {
+ case gonf.BROKEN:
+ b.status = gonf.BROKEN
+ return
+ case gonf.REPAIRED:
+ b.status = gonf.REPAIRED
+ }
+ // borg package
+ switch installBorgPackage() {
+ case gonf.BROKEN:
+ b.status = gonf.BROKEN
+ return
+ case gonf.REPAIRED:
+ b.status = gonf.REPAIRED
+ }
+ // authorized_keys
+ borgDir := gonf.ModeUserGroup(0700, "borg", "borg")
+ borgRO := gonf.ModeUserGroup(0400, "borg", "borg")
+ file := gonf.File(filepath.Join(b.path, ".ssh/authorized_keys")).
+ DirectoriesPermissions(borgDir).
+ Permissions(borgRO)
+ authorizedKeys := ""
+ // we sort the names so that the file contents are stable
+ names := make([]string, len(b.clients)-1)
+ for name := range b.clients {
+ names = append(names, name)
+ }
+ for _, name := range names {
+ key := b.clients[name]
+ authorizedKeys += "command=\"borg serve --restrict-to-path " + filepath.Join(b.path, name) + "\",restrict " + string(key) + "\n"
+ }
+ file.Contents(authorizedKeys).Resolve()
+ switch file.Status() {
+ case gonf.BROKEN:
+ b.status = gonf.BROKEN
+ return
+ case gonf.REPAIRED:
+ b.status = gonf.REPAIRED
+ }
+ // TODO init repositories? or let the borg client do it?
+}
+
+func (b BorgServer) Status() gonf.Status {
+ return b.status
+}
+
+func Server() *BorgServer {
+ return &BorgServer{
+ chain: nil,
+ clients: make(map[string][]byte),
+ path: "/srv/borg/",
+ user: "borg",
+ status: gonf.PROMISED,
+ }
+}
+
+func (b *BorgServer) Add(name string, publicKey []byte) *BorgServer {
+ if _, ok := b.clients[name]; ok {
+ slog.Debug("Duplicate name for BorgServer", "name", name)
+ panic("Duplicate name for BorgServer")
+ }
+ b.clients[name] = publicKey
+ return b
+}