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
package kcpdatastore
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"time"
)
type Role string
type Kind string
const (
RoleGateway Role = "gateway"
RoleBidder Role = "bidder"
KindKineSqlite Kind = "kine-sqlite"
KindKinePostgres Kind = "kine-postgres"
)
const (
SqliteFile = "kine.db"
BackupsDir = "backups"
BackupTimestamp = "20060102T150405Z"
)
var SqliteCompanions = []string{"-wal", "-shm"}
type Assets struct {
Kcp string
Kine string
}
func StoreFor(role Role, profile string) (Kind, error) {
switch profile {
case "", "quick", "market-network", "prodlike":
default:
return "", fmt.Errorf("kcpdatastore: unknown profile %q", profile)
}
switch role {
case RoleBidder:
return KindKineSqlite, nil
case RoleGateway:
if profile == "quick" {
return KindKineSqlite, nil
}
return KindKinePostgres, nil
}
return "", fmt.Errorf("kcpdatastore: unknown role %q", role)
}
func NeedsBackup(from, to Assets) bool {
return from.Kcp != to.Kcp || from.Kine != to.Kine
}
func copyChecked(src, dst string) (int64, error) {
in, err := os.Open(src)
if err != nil {
return 0, err
}
defer in.Close()
out, err := os.OpenFile(dst, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600)
if err != nil {
return 0, err
}
n, err := io.Copy(out, in)
if err != nil {
out.Close()
return n, err
}
if err := out.Sync(); err != nil {
out.Close()
return n, err
}
return n, out.Close()
}
func BackupSqlite(dir string, at time.Time) (string, error) {
db := filepath.Join(dir, SqliteFile)
fi, err := os.Stat(db)
if err != nil {
return "", fmt.Errorf("kcpdatastore: no sqlite store to copy: %w", err)
}
if fi.Size() == 0 {
return "", fmt.Errorf("kcpdatastore: %s is empty; refusing a backup that restores nothing", db)
}
out := filepath.Join(dir, BackupsDir, at.UTC().Format(BackupTimestamp))
if err := os.MkdirAll(out, 0o700); err != nil {
return "", err
}
n, err := copyChecked(db, filepath.Join(out, SqliteFile))
if err != nil {
return "", fmt.Errorf("kcpdatastore: copying %s: %w", db, err)
}
if n != fi.Size() || n == 0 {
return "", fmt.Errorf("kcpdatastore: copied %d of %d bytes of %s", n, fi.Size(), db)
}
for _, suffix := range SqliteCompanions {
src := db + suffix
if _, err := os.Stat(src); errors.Is(err, os.ErrNotExist) {
continue
}
if _, err := copyChecked(src, filepath.Join(out, SqliteFile+suffix)); err != nil {
return "", fmt.Errorf("kcpdatastore: copying %s: %w", src, err)
}
}
return out, nil
}