Commit 03e5ee3161
03e5ee3161bdeff7a3b87d3566dbe9fbe6ff67f1
parent: 7e5de5325c
Verified · cmc
cmc <hello@cleberg.net> · 2026-09-28 21:21 UTC
config: [backup] age_recipients (filippo.io/age v1.3.2)
Ref #274
Layout: unified · split
go.mod
+2
| @@ -3,6 +3,7 @@ module gitbay.org/gitbay |
| 3 | go 1.27.0 |
3 | go 1.27.0 |
| 4 | |
4 | |
| 5 | require ( |
5 | require ( |
| |
6 | filippo.io/age v1.3.2 |
| 6 | github.com/BurntSushi/toml v1.6.0 |
7 | github.com/BurntSushi/toml v1.6.0 |
| 7 | github.com/ProtonMail/go-crypto v1.5.1 |
8 | github.com/ProtonMail/go-crypto v1.5.1 |
| 8 | github.com/alecthomas/chroma/v2 v2.27.0 |
9 | github.com/alecthomas/chroma/v2 v2.27.0 |
| @@ -21,6 +22,7 @@ require ( |
| 21 | ) |
22 | ) |
| 22 | |
23 | |
| 23 | require ( |
24 | require ( |
| |
25 | filippo.io/hpke v0.4.0 // indirect |
| 24 | github.com/aymerick/douceur v0.2.0 // indirect |
26 | github.com/aymerick/douceur v0.2.0 // indirect |
| 25 | github.com/cloudflare/circl v1.6.3 // indirect |
27 | github.com/cloudflare/circl v1.6.3 // indirect |
| 26 | github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect |
28 | github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect |
go.sum
+6
| @@ -1,3 +1,9 @@ |
| |
1 | c2sp.org/CCTV/age v0.0.0-20260829155415-4448f2097b2d h1:Blprhc2SbChNZtWcU+BLTM4YdoqYAS9V7cJgOwJKyAs= |
| |
2 | c2sp.org/CCTV/age v0.0.0-20260829155415-4448f2097b2d/go.mod h1:SrHC2C7r5GkDk8R+NFVzYy/sdj0Ypg9htaPXQq5Cqeo= |
| |
3 | filippo.io/age v1.3.2 h1:r6RSZLFSMm6rzKepZ7ZAYkKCu14f3/Me8c7uKYh7C8c= |
| |
4 | filippo.io/age v1.3.2/go.mod h1:TH/Yr2sSRhCKbaH4XPxpUV0Us8Gv6txYUpiZQWz8Evk= |
| |
5 | filippo.io/hpke v0.4.0 h1:p575VVQ6ted4pL+it6M00V/f2qTZITO0zgmdKCkd5+A= |
| |
6 | filippo.io/hpke v0.4.0/go.mod h1:EmAN849/P3qdeK+PCMkDpDm83vRHM5cDipBJ8xbQLVY= |
| 1 | github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= |
7 | github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= |
| 2 | github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= |
8 | github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= |
| 3 | github.com/ProtonMail/go-crypto v1.5.1 h1:pTrLDQHyOT8y3DFYIpijgPBTw/7E2GLMimutvOlceuE= |
9 | github.com/ProtonMail/go-crypto v1.5.1 h1:pTrLDQHyOT8y3DFYIpijgPBTw/7E2GLMimutvOlceuE= |
internal/config/config.go
+27
| @@ -14,6 +14,7 @@ import ( |
| 14 | "strings" |
14 | "strings" |
| 15 | "time" |
15 | "time" |
| 16 | |
16 | |
| |
17 | "filippo.io/age" |
| 17 | "github.com/BurntSushi/toml" |
18 | "github.com/BurntSushi/toml" |
| 18 | ) |
19 | ) |
| 19 | |
20 | |
| @@ -40,6 +41,7 @@ type Config struct { |
| 40 | Deps Deps `toml:"deps"` |
41 | Deps Deps `toml:"deps"` |
| 41 | Retention Retention `toml:"retention"` |
42 | Retention Retention `toml:"retention"` |
| 42 | Push Push `toml:"push"` |
43 | Push Push `toml:"push"` |
| |
44 | Backup Backup `toml:"backup"` |
| 43 | // GoImport maps vanity Go module paths to repositories, e.g. |
45 | // GoImport maps vanity Go module paths to repositories, e.g. |
| 44 | // "gitbay.org/gitbay" = "krz/gitbay". Requests carrying ?go-get=1 |
46 | // "gitbay.org/gitbay" = "krz/gitbay". Requests carrying ?go-get=1 |
| 45 | // under a mapped path get a go-import meta tag. |
47 | // under a mapped path get a go-import meta tag. |
| @@ -297,6 +299,27 @@ func LoadAPNSKey(path string) (*ecdsa.PrivateKey, error) { |
| 297 | return key, nil |
299 | return key, nil |
| 298 | } |
300 | } |
| 299 | |
301 | |
| |
302 | // Backup configures gitbayd admin backup. |
| |
303 | type Backup struct { |
| |
304 | // AgeRecipients, when set, encrypts every archive to these age |
| |
305 | // public keys (age1...). The matching identities stay off the host, |
| |
306 | // so the host writes archives it cannot read. |
| |
307 | AgeRecipients []string `toml:"age_recipients"` |
| |
308 | } |
| |
309 | |
| |
310 | // Recipients parses AgeRecipients. |
| |
311 | func (b Backup) Recipients() ([]age.Recipient, error) { |
| |
312 | var rs []age.Recipient |
| |
313 | for _, s := range b.AgeRecipients { |
| |
314 | r, err := age.ParseX25519Recipient(s) |
| |
315 | if err != nil { |
| |
316 | return nil, fmt.Errorf("backup.age_recipients: %q: %w", s, err) |
| |
317 | } |
| |
318 | rs = append(rs, r) |
| |
319 | } |
| |
320 | return rs, nil |
| |
321 | } |
| |
322 | |
| 300 | // Default returns the configuration used when a key is absent from the file. |
323 | // Default returns the configuration used when a key is absent from the file. |
| 301 | func Default() Config { |
324 | func Default() Config { |
| 302 | return Config{ |
325 | return Config{ |
| @@ -479,6 +502,10 @@ func (c Config) Validate() error { |
| 479 | } |
502 | } |
| 480 | } |
503 | } |
| 481 | |
504 | |
| |
505 | if _, err := c.Backup.Recipients(); err != nil { |
| |
506 | errs = append(errs, err) |
| |
507 | } |
| |
508 | |
| 482 | // Contradictions. |
509 | // Contradictions. |
| 483 | if c.Mail.SMTPHost != "" && c.Mail.From == "" { |
510 | if c.Mail.SMTPHost != "" && c.Mail.From == "" { |
| 484 | errs = append(errs, errors.New("[mail] from is required when smtp_host is set")) |
511 | errs = append(errs, errors.New("[mail] from is required when smtp_host is set")) |
internal/config/config_test.go
+23
| @@ -10,6 +10,8 @@ import ( |
| 10 | "path/filepath" |
10 | "path/filepath" |
| 11 | "strings" |
11 | "strings" |
| 12 | "testing" |
12 | "testing" |
| |
13 | |
| |
14 | "filippo.io/age" |
| 13 | ) |
15 | ) |
| 14 | |
16 | |
| 15 | func writeConfig(t *testing.T, body string) string { |
17 | func writeConfig(t *testing.T, body string) string { |
| @@ -366,3 +368,24 @@ func TestSecretKeyFileSymlinks(t *testing.T) { |
| 366 | } |
368 | } |
| 367 | }) |
369 | }) |
| 368 | } |
370 | } |
| |
371 | |
| |
372 | func TestBackupRecipients(t *testing.T) { |
| |
373 | id, err := age.GenerateX25519Identity() |
| |
374 | if err != nil { |
| |
375 | t.Fatal(err) |
| |
376 | } |
| |
377 | cfg, err := Load(writeConfig(t, minimal+"[backup]\nage_recipients = [\""+id.Recipient().String()+"\"]\n")) |
| |
378 | if err != nil { |
| |
379 | t.Fatal(err) |
| |
380 | } |
| |
381 | rs, err := cfg.Backup.Recipients() |
| |
382 | if err != nil || len(rs) != 1 { |
| |
383 | t.Fatalf("Recipients = %v, %v", rs, err) |
| |
384 | } |
| |
385 | if _, err := Load(writeConfig(t, minimal+"[backup]\nage_recipients = [\"age1notakey\"]\n")); err == nil || !strings.Contains(err.Error(), "backup.age_recipients") { |
| |
386 | t.Fatalf("a malformed recipient: %v", err) |
| |
387 | } |
| |
388 | if cfg, err := Load(writeConfig(t, minimal)); err != nil || len(cfg.Backup.AgeRecipients) != 0 { |
| |
389 | t.Fatalf("default: %v, %v", cfg.Backup, err) |
| |
390 | } |
| |
391 | } |