Skip to content

Commit f37c697

Browse files
Merge pull request #1333 from signal18/feature/restic-02-configuration
feat: add restic configuration wiring and mount auto-disable
2 parents 81c8e15 + 1b741c8 commit f37c697

File tree

10 files changed

+218
-6
lines changed

10 files changed

+218
-6
lines changed

cluster/cluster_bck.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,14 @@ func (cluster *Cluster) StartResticManager() error {
101101
return nil
102102
}
103103

104-
cluster.ResticManager = backupmgr.NewResticRepo(cluster.Conf.BackupResticBinaryPath, cluster.MessageChan, config.ConstLogModRestic)
104+
resticManager := backupmgr.NewResticRepo(cluster.Conf.BackupResticBinaryPath, cluster.MessageChan, config.ConstLogModRestic)
105+
if err := cluster.Conf.ValidateResticPermissions(); err != nil {
106+
cluster.LogModulePrintf(cluster.Conf.Verbose, config.ConstLogModGeneral, config.LvlWarn, "Invalid restic permission config: %s", err)
107+
}
108+
resticManager.SetPermissions(cluster.Conf.GetResticDirMode(), cluster.Conf.GetResticFileMode())
109+
resticManager.SetOperationTimeout(cluster.Conf.GetResticTimeout())
110+
resticManager.AutoDetectAndDisableMount()
111+
cluster.ResticManager = resticManager
105112
cluster.ReloadResticEnv()
106113
go cluster.ResticFetchRepo()
107114
return nil

config/config.go

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,8 @@ type Config struct {
743743
BackupResticPassword string `mapstructure:"backup-restic-password" toml:"backup-restic-password" json:"-"`
744744
BackupResticAws bool `mapstructure:"backup-restic-aws" toml:"backup-restic-aws" json:"backupResticAws"`
745745
BackupResticTimeout int `mapstructure:"backup-restic-timeout" toml:"backup-restic-timeout" json:"backupResticTimeout"`
746+
BackupResticDirMode int `mapstructure:"backup-restic-dir-mode" toml:"backup-restic-dir-mode" json:"backupResticDirMode"`
747+
BackupResticFileMode int `mapstructure:"backup-restic-file-mode" toml:"backup-restic-file-mode" json:"backupResticFileMode"`
746748
BackupResticPurgeOldestOnDiskSpace bool `mapstructure:"backup-restic-purge-oldest-on-disk-space" toml:"backup-restic-purge-oldest-on-disk-space" json:"backupResticPurgeOldestOnDiskSpace"`
747749
BackupResticPurgeOldestOnDiskThreshold int `mapstructure:"backup-restic-purge-oldest-on-disk-threshold" toml:"backup-restic-purge-oldest-on-disk-treshold" json:"backupResticPurgeOldestOnDiskTreshold"`
748750
BackupStreaming bool `mapstructure:"backup-streaming" toml:"backup-streaming" json:"backupStreaming"`
@@ -1759,7 +1761,6 @@ func (conf *Config) GenerateKey(Logger *logrus.Logger) error {
17591761
conf.MonitoringKeyPath = fallbackPath
17601762
Logger.Debugf("Path writable. Flag 'monitoring-key-path' set to: %s.", fallbackPath)
17611763
Logger.Debugf("Generating key on: %s", conf.MonitoringKeyPath)
1762-
17631764
}
17641765

17651766
p := crypto.Password{}
@@ -3787,6 +3788,58 @@ func (conf *Config) CheckKeepWithin() error {
37873788
return nil
37883789
}
37893790

3791+
func isValidResticMode(value int) bool {
3792+
if value == 0 {
3793+
return true
3794+
}
3795+
if value < 600 || value > 777 {
3796+
return false
3797+
}
3798+
_, err := strconv.ParseUint(strconv.Itoa(value), 8, 32)
3799+
return err == nil
3800+
}
3801+
3802+
func parseResticMode(value int, defaultMode os.FileMode) os.FileMode {
3803+
if value <= 0 {
3804+
return defaultMode
3805+
}
3806+
if !isValidResticMode(value) {
3807+
return defaultMode
3808+
}
3809+
3810+
parsed, err := strconv.ParseUint(strconv.Itoa(value), 8, 32)
3811+
if err != nil {
3812+
return defaultMode
3813+
}
3814+
3815+
return os.FileMode(parsed)
3816+
}
3817+
3818+
func (conf *Config) ValidateResticPermissions() error {
3819+
if !isValidResticMode(conf.BackupResticDirMode) {
3820+
return NewValidationError("backup-restic-dir-mode", conf.BackupResticDirMode, "expected octal value in 6xx/7xx range, like 700")
3821+
}
3822+
if !isValidResticMode(conf.BackupResticFileMode) {
3823+
return NewValidationError("backup-restic-file-mode", conf.BackupResticFileMode, "expected octal value in 6xx/7xx range, like 600")
3824+
}
3825+
return nil
3826+
}
3827+
3828+
func (conf *Config) GetResticDirMode() os.FileMode {
3829+
return parseResticMode(conf.BackupResticDirMode, 0700)
3830+
}
3831+
3832+
func (conf *Config) GetResticFileMode() os.FileMode {
3833+
return parseResticMode(conf.BackupResticFileMode, 0600)
3834+
}
3835+
3836+
func (conf *Config) GetResticTimeout() time.Duration {
3837+
if conf.BackupResticTimeout <= 0 {
3838+
return 2 * time.Hour
3839+
}
3840+
return time.Duration(conf.BackupResticTimeout) * time.Second
3841+
}
3842+
37903843
type MeasurementConfig struct {
37913844
Min int
37923845
Max int

config/config_v2.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ type ConfigV2 struct {
2323
WithEmbed string `mapstructure:"-" toml:"-" json:"withEmbed"`
2424

2525
// Domain-Specific Configurations
26-
Monitoring MonitoringConfig `mapstructure:",squash" toml:",inline" json:"monitoring"`
27-
Database DatabaseConfig `mapstructure:",squash" toml:",inline" json:"database"`
28-
Replication ReplicationConfig `mapstructure:",squash" toml:",inline" json:"replication"`
29-
Failover FailoverConfig `mapstructure:",squash" toml:",inline" json:"failover"`
26+
Monitoring MonitoringConfig `mapstructure:",squash" toml:",inline" json:"monitoring"`
27+
Database DatabaseConfig `mapstructure:",squash" toml:",inline" json:"database"`
28+
Replication ReplicationConfig `mapstructure:",squash" toml:",inline" json:"replication"`
29+
Failover FailoverConfig `mapstructure:",squash" toml:",inline" json:"failover"`
3030
// TODO: Add remaining domain configs:
3131
// Switchover SwitchoverConfig `mapstructure:",squash" toml:",inline" json:"switchover"`
3232
// Backup BackupConfig `mapstructure:",squash" toml:",inline" json:"backup"`

config/restic_permissions_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package config
2+
3+
import (
4+
"os"
5+
"testing"
6+
)
7+
8+
func TestParseResticMode(t *testing.T) {
9+
defaultMode := os.FileMode(0700)
10+
11+
if got := parseResticMode(700, defaultMode); got != 0700 {
12+
t.Fatalf("expected 700 to parse as 0700, got %#o", got)
13+
}
14+
if got := parseResticMode(600, defaultMode); got != 0600 {
15+
t.Fatalf("expected 600 to parse as 0600, got %#o", got)
16+
}
17+
if got := parseResticMode(755, defaultMode); got != 0755 {
18+
t.Fatalf("expected 755 to parse as 0755, got %#o", got)
19+
}
20+
if got := parseResticMode(400, defaultMode); got != defaultMode {
21+
t.Fatalf("expected 400 to fallback to default, got %#o", got)
22+
}
23+
if got := parseResticMode(888, defaultMode); got != defaultMode {
24+
t.Fatalf("expected 888 to fallback to default, got %#o", got)
25+
}
26+
}
27+
28+
func TestValidateResticPermissions(t *testing.T) {
29+
conf := &Config{}
30+
31+
conf.BackupResticDirMode = 700
32+
conf.BackupResticFileMode = 600
33+
if err := conf.ValidateResticPermissions(); err != nil {
34+
t.Fatalf("expected valid permissions, got error: %v", err)
35+
}
36+
37+
conf.BackupResticDirMode = 400
38+
conf.BackupResticFileMode = 600
39+
if err := conf.ValidateResticPermissions(); err == nil {
40+
t.Fatalf("expected error for invalid dir mode 400")
41+
}
42+
43+
conf.BackupResticDirMode = 700
44+
conf.BackupResticFileMode = 888
45+
if err := conf.ValidateResticPermissions(); err == nil {
46+
t.Fatalf("expected error for invalid file mode 888")
47+
}
48+
49+
conf.BackupResticDirMode = 0
50+
conf.BackupResticFileMode = 0
51+
if err := conf.ValidateResticPermissions(); err != nil {
52+
t.Fatalf("expected zero values to be valid, got error: %v", err)
53+
}
54+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Restic Permission Validation
2+
3+
## Summary
4+
5+
Restic permission modes accept only octal digit values in the 6xx or 7xx range
6+
(e.g., 600, 700, 750, 755). This keeps configuration consistent with OpenSVC's
7+
permission inputs (string values like "700"/"600").
8+
9+
## Entry Points
10+
11+
- config/config.go
12+
- parseResticMode() converts octal digit values to os.FileMode.
13+
- ValidateResticPermissions() enforces allowed ranges and returns
14+
configuration validation errors.
15+
- server/api_cluster.go
16+
- API updates for backup-restic-dir-mode and backup-restic-file-mode
17+
validate inputs and return errors if invalid.
18+
- cluster/cluster_bck.go
19+
- StartResticManager() calls ValidateResticPermissions() and logs a warning
20+
on invalid inputs.
21+
22+
## Rules
23+
24+
- Valid values: 600-777 (octal digits only)
25+
- Zero value: 0 means "use defaults" (0700 for dirs, 0600 for files)
26+
- Invalid values (examples): 400, 888, -1, 0o700 (parsed to 448 and rejected)
27+
28+
## Behavior
29+
30+
- API updates with invalid values return errors and do not apply changes.
31+
- Invalid values from config files are rejected by validation; defaults are
32+
used when values are zero or invalid.
33+
34+
## Rationale
35+
36+
- Aligns with OpenSVC permission formatting (string octal digits).
37+
- Avoids ambiguous interpretation of decimal values.
38+
- Ensures stronger defaults for security (owner-only permissions).
39+
40+
## Examples
41+
42+
Valid:
43+
- backup-restic-dir-mode = 700
44+
- backup-restic-file-mode = 600
45+
46+
Invalid:
47+
- backup-restic-dir-mode = 400
48+
- backup-restic-file-mode = 888
49+

etc/config.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ include = "/etc/replication-manager/cluster.d"
177177
# backup-mydumper-path = "/usr/bin/mydumper"
178178
# backup-myloader-path = "/usr/bin/myloader"
179179
# backup-restic-binary-path = "/usr/bin/restic"
180+
# backup-restic-timeout = 7200
181+
# backup-restic-dir-mode = 700
182+
# backup-restic-file-mode = 600
180183
# haproxy-binary-path = "/usr/sbin/haproxy"
181184
# maxscale-binary-path = "/usr/sbin/maxscale"
182185

etc/local/features/backup-s3/config.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ backup-restic-aws = true
6464
backup-restic-aws-access-secret = "xxxx"
6565
backup-restic-password = "xxxx"
6666
backup-restic-binary-path = "/usr/local/bin/restic"
67+
backup-restic-timeout = 7200
68+
backup-restic-dir-mode = 700
69+
backup-restic-file-mode = 600
6770

6871
monitoring-scheduler = true
6972
scheduler-db-servers-logical-backup = true

etc/local/features/backup/config.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ backup-physical-type = "mariabackup"
7171
backup-restic-aws-access-secret = "xxxx"
7272
backup-restic-password = "xxxx"
7373
backup-restic-binary-path = "/usr/local/bin/restic"
74+
backup-restic-timeout = 7200
75+
backup-restic-dir-mode = 700
76+
backup-restic-file-mode = 600
7477

7578
monitoring-scheduler = true
7679
scheduler-db-servers-logical-backup = true

server/api_cluster.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2803,6 +2803,43 @@ func (repman *ReplicationManager) setClusterSetting(mycluster *cluster.Cluster,
28032803
case "backup-restic-purge-oldest-on-disk-threshold":
28042804
val, _ := strconv.Atoi(value)
28052805
mycluster.Conf.BackupResticPurgeOldestOnDiskThreshold = val
2806+
case "backup-restic-timeout":
2807+
val, err := strconv.Atoi(value)
2808+
if err != nil {
2809+
return fmt.Errorf("invalid value for backup-restic-timeout: %q", value)
2810+
}
2811+
mycluster.Conf.BackupResticTimeout = val
2812+
if mycluster.ResticManager != nil {
2813+
mycluster.ResticManager.SetOperationTimeout(mycluster.Conf.GetResticTimeout())
2814+
}
2815+
case "backup-restic-dir-mode":
2816+
val, err := strconv.Atoi(value)
2817+
if err != nil {
2818+
return fmt.Errorf("invalid value for backup-restic-dir-mode: %q", value)
2819+
}
2820+
oldValue := mycluster.Conf.BackupResticDirMode
2821+
mycluster.Conf.BackupResticDirMode = val
2822+
if err := mycluster.Conf.ValidateResticPermissions(); err != nil {
2823+
mycluster.Conf.BackupResticDirMode = oldValue
2824+
return err
2825+
}
2826+
if mycluster.ResticManager != nil {
2827+
mycluster.ResticManager.SetPermissions(mycluster.Conf.GetResticDirMode(), mycluster.Conf.GetResticFileMode())
2828+
}
2829+
case "backup-restic-file-mode":
2830+
val, err := strconv.Atoi(value)
2831+
if err != nil {
2832+
return fmt.Errorf("invalid value for backup-restic-file-mode: %q", value)
2833+
}
2834+
oldValue := mycluster.Conf.BackupResticFileMode
2835+
mycluster.Conf.BackupResticFileMode = val
2836+
if err := mycluster.Conf.ValidateResticPermissions(); err != nil {
2837+
mycluster.Conf.BackupResticFileMode = oldValue
2838+
return err
2839+
}
2840+
if mycluster.ResticManager != nil {
2841+
mycluster.ResticManager.SetPermissions(mycluster.Conf.GetResticDirMode(), mycluster.Conf.GetResticFileMode())
2842+
}
28062843
case "backup-logical-type":
28072844
mycluster.SetBackupLogicalType(value)
28082845
case "backup-physical-type":

server/server.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,9 @@ func (repman *ReplicationManager) AddFlags(flags *pflag.FlagSet, conf *config.Co
796796
flags.StringVar(&conf.BackupResticRepository, "backup-restic-repository", "s3:https://s3.signal18.io/backups", "Restic backend repository")
797797
flags.StringVar(&conf.BackupResticPassword, "backup-restic-password", "secret", "Restic backend password")
798798
flags.BoolVar(&conf.BackupResticAws, "backup-restic-aws", false, "Restic will archive to s3 or to datadir/backups/archive")
799+
flags.IntVar(&conf.BackupResticTimeout, "backup-restic-timeout", 7200, "Restic operation timeout in seconds")
800+
flags.IntVar(&conf.BackupResticDirMode, "backup-restic-dir-mode", 700, "Restic directory permissions (octal, e.g. 700)")
801+
flags.IntVar(&conf.BackupResticFileMode, "backup-restic-file-mode", 600, "Restic file permissions (octal, e.g. 600)")
799802
flags.BoolVar(&conf.BackupResticPurgeOldestOnDiskSpace, "backup-restic-purge-oldest-on-disk-space", true, "Restic will purge oldest backup when disk space is critically low")
800803
flags.IntVar(&conf.BackupResticPurgeOldestOnDiskThreshold, "backup-restic-purge-oldest-on-disk-threshold", 0, "Restic will purge oldest backup when disk space used is above this percentage. 0 means use backup-disk-threshold-crit value")
801804
flags.BoolVar(&conf.BackupStreaming, "backup-streaming", false, "Backup streaming to cloud ")

0 commit comments

Comments
 (0)