summaryrefslogtreecommitdiff
path: root/stdlib/backups/borg/client.go
blob: a203ae2453fadac16a9dbfa9fe55735cc335276c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package borg

import (
	_ "embed"
	"fmt"
	"os"

	"log/slog"
	"path/filepath"

	gonf "git.adyxax.org/adyxax/gonf/v2/pkg"
)

//go:embed borg-script-template
var borg_script_template string

//go:embed systemd-service-template
var systemd_service_template string

//go:embed systemd-timer-template
var systemd_timer_template string

type Job struct {
	hostname   string
	path       string
	privateKey []byte
}

type BorgClient struct {
	chain  []gonf.Promise
	jobs   map[string]*Job // name -> privateKey
	path   string
	status gonf.Status
}

func (b *BorgClient) IfRepaired(p ...gonf.Promise) gonf.Promise {
	b.chain = append(b.chain, p...)
	return b
}

func (b *BorgClient) Promise() *BorgClient {
	gonf.MakeCustomPromise(b).Promise()
	return b
}

func (b *BorgClient) Resolve() {
	b.status = gonf.KEPT
	// borg package
	switch installBorgPackage() {
	case gonf.BROKEN:
		b.status = gonf.BROKEN
		return
	case gonf.REPAIRED:
		b.status = gonf.REPAIRED
	}
	// private key
	rootDir := gonf.ModeUserGroup(0700, "root", "root")
	rootRO := gonf.ModeUserGroup(0400, "root", "root")
	rootRX := gonf.ModeUserGroup(0500, "root", "root")
	gonf.Directory("/root/.cache/borg").DirectoriesPermissions(rootDir).Resolve()
	gonf.Directory("/root/.config/borg").DirectoriesPermissions(rootDir).Resolve()
	systemdSystemPath := "/etc/systemd/system/"
	hostname, err := os.Hostname()
	if err != nil {
		slog.Error("Unable to find current hostname", "err", err)
		hostname = "unknown"
	}
	for name, job := range b.jobs {
		gonf.File(filepath.Join(b.path, fmt.Sprintf("%s.key", name))).
			DirectoriesPermissions(rootDir).
			Permissions(rootRO).Contents(job.privateKey).
			Resolve()
		gonf.File(filepath.Join(b.path, fmt.Sprintf("%s.sh", name))).
			DirectoriesPermissions(rootDir).
			Permissions(rootRX).Contents(fmt.Sprintf(borg_script_template,
			hostname,
			name,
			job.path,
			job.hostname, name)).
			Resolve()
		service_name := fmt.Sprintf("borgbackup-job-%s.service", name)
		gonf.File(filepath.Join(systemdSystemPath, service_name)).
			DirectoriesPermissions(rootDir).
			Permissions(rootRO).Contents(fmt.Sprintf(systemd_service_template,
			name,
			job.hostname, name,
			name,
			name)).
			Resolve()
		timer_name := fmt.Sprintf("borgbackup-job-%s.timer", name)
		gonf.File(filepath.Join(systemdSystemPath, timer_name)).
			DirectoriesPermissions(rootDir).
			Permissions(rootRO).Contents(fmt.Sprintf(systemd_timer_template, name)).
			Resolve()
		gonf.Service(timer_name).State("enabled", "started").Resolve()
	}
}

func (b BorgClient) Status() gonf.Status {
	return b.status
}

func Client() *BorgClient {
	return &BorgClient{
		chain:  nil,
		jobs:   make(map[string]*Job),
		path:   "/etc/borg/",
		status: gonf.PROMISED,
	}
}

func (b *BorgClient) Add(name string, path string, privateKey []byte, hostname string) *BorgClient {
	if _, ok := b.jobs[name]; ok {
		slog.Debug("Duplicate name for BorgClient", "name", name)
		panic("Duplicate name for BorgClient")
	}
	b.jobs[name] = &Job{
		hostname:   hostname,
		path:       path,
		privateKey: privateKey,
	}
	return b
}