Skip to content

Commit 88cb74f

Browse files
run shell commands before and after a profile
In previous versions, you could only run commands before and after a backup
1 parent d71f45f commit 88cb74f

File tree

7 files changed

+100
-18
lines changed

7 files changed

+100
-18
lines changed

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,5 @@ rest-server:
110110

111111
nightly:
112112
go install github.com/goreleaser/goreleaser
113+
go mod tidy
113114
goreleaser --snapshot --skip-publish --rm-dist

README.md

+29-13
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ With resticprofile:
1515
* You can check a repository before or after a backup
1616
* You can create groups of profiles that will run sequentially
1717
* You can run shell commands before or after a backup
18+
* You can also run shell commands before or after running a profile (any command): useful if you need to mount your backup disk
1819
* You can send a backup stream via _stdin_
1920
* You can start restic at a lower or higher priority (Priority Class in Windows, *nice* in all unixes) and/or _ionice_ (only available on Linux)
2021

@@ -33,7 +34,7 @@ It's been actively tested on macOS X and Linux, and regularly tested on Windows.
3334

3435
**This is at _beta_ stage. Please don't use it in production yet. Even though I'm using it on my servers, I cannot guarantee all combinations of configuration are going to work properly for you.**
3536

36-
## Install
37+
## Installation
3738

3839
Here's a simple script to download the binary automatically for you. It works on mac OS X, FreeBSD, OpenBSD and Linux:
3940

@@ -59,14 +60,19 @@ It will install resticprofile in `/usr/local/bin/`
5960
- Download the package corresponding to your system and CPU from the [release page](https://github.com/creativeprojects/resticprofile/releases)
6061
- Once downloaded you need to open the archive and copy the binary file `resticprofile` (or `resticprofile.exe`) in your PATH.
6162

62-
## Update
63+
## Upgrade
6364

64-
Once installed, you can easily update resticprofile to the latest release using this command:
65+
Once installed, you can easily upgrade resticprofile to the latest release using this flag:
6566

6667
```
6768
$ resticprofile --self-update
6869
```
6970

71+
Starting with version 0.7.0 you can also upgrade resticprofile using this command:
72+
73+
```
74+
$ resticprofile self-update
75+
```
7076

7177
## Using docker image ##
7278

@@ -193,6 +199,9 @@ full-backup = [ "root", "src" ]
193199
repository = "/backup"
194200
password-file = "key"
195201
initialize = false
202+
# will run these scripts before and after each command (including 'backup')
203+
run-before = "mount /backup"
204+
run-after = "umount /backup"
196205

197206
[default.env]
198207
TMPDIR= "/tmp"
@@ -247,14 +256,15 @@ initialize = true
247256

248257
# 'backup' command of profile 'src'
249258
[src.backup]
250-
run-before = [ "echo Starting!", "ls -al ./src" ]
251-
run-after = "echo All Done!"
252259
exclude = [ '/**/.git' ]
253260
exclude-caches = true
254261
one-file-system = false
255262
tag = [ "test", "dev" ]
256263
source = [ "./src" ]
257264
check-before = true
265+
# will only run these scripts before and after a backup
266+
run-before = [ "echo Starting!", "ls -al ./src" ]
267+
run-after = "echo All Done!"
258268

259269
# retention policy for profile src
260270
[src.retention]
@@ -325,15 +335,15 @@ See all available profiles in your configuration file (and the restic commands w
325335
$ resticprofile profiles
326336
327337
Profiles available:
328-
stdin (snapshots, backup)
329-
src (retention, backup, snapshots)
330-
root (backup, retention)
331-
documents (snapshots, backup)
332-
default (env)
333-
self (backup, snapshots)
338+
stdin: (backup)
339+
default: (env)
340+
root: (retention, backup)
341+
src: (retention, backup)
342+
linux: (retention, backup, snapshots, env)
343+
no-cache: (n/a)
334344
335345
Groups available:
336-
full-backup: root, src
346+
full-backup: root, src
337347
338348
```
339349

@@ -369,10 +379,14 @@ resticprofile flags:
369379
-n, --name string profile name (default "default")
370380
--no-ansi disable ansi control characters (disable console colouring)
371381
-q, --quiet display only warnings and errors
372-
--self-update auto update of resticprofile (does not update restic)
373382
--theme string console colouring theme (dark, light, none) (default "light")
374383
-v, --verbose display all debugging information
375384
385+
resticprofile own commands:
386+
profiles display profile names from the configuration file
387+
self-update update resticprofile to latest version (does not update restic)
388+
systemd-unit create a user systemd timer
389+
376390
```
377391

378392
A command is a restic command **except** for one command recognized by resticprofile only: `profiles`
@@ -417,6 +431,8 @@ Flags used by resticprofile only
417431
* ****inherit****: string
418432
* **initialize**: true / false
419433
* **lock**: string: specify a local lockfile
434+
* **run-before**: string OR list of strings
435+
* **run-after**: string OR list of strings
420436

421437
Flags passed to the restic command line
422438

config/profile.go

+16-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"path/filepath"
77
"strings"
88

9+
"github.com/creativeprojects/resticprofile/clog"
910
"github.com/creativeprojects/resticprofile/constants"
1011
"github.com/spf13/viper"
1112
)
@@ -23,6 +24,8 @@ type Profile struct {
2324
Initialize bool `mapstructure:"initialize"`
2425
Inherit string `mapstructure:"inherit"`
2526
Lock string `mapstructure:"lock"`
27+
RunBefore []string `mapstructure:"run-before"`
28+
RunAfter []string `mapstructure:"run-after"`
2629
Environment map[string]string `mapstructure:"env"`
2730
Backup *BackupSection `mapstructure:"backup"`
2831
Retention *RetentionSection `mapstructure:"retention"`
@@ -185,17 +188,25 @@ func (p *Profile) GetCommandFlags(command string) map[string][]string {
185188

186189
switch command {
187190
case constants.CommandBackup:
191+
if p.Backup == nil {
192+
clog.Warning("No definition for backup command in this profile")
193+
break
194+
}
188195
commandFlags := convertStructToFlags(*p.Backup)
189196
if commandFlags != nil && len(commandFlags) > 0 {
190197
flags = mergeFlags(flags, commandFlags)
191198
}
192199
flags = addOtherFlags(flags, p.Backup.OtherFlags)
193200

194201
case constants.CommandSnapshots:
195-
flags = addOtherFlags(flags, p.Snapshots)
202+
if p.Snapshots != nil {
203+
flags = addOtherFlags(flags, p.Snapshots)
204+
}
196205

197206
case constants.CommandCheck:
198-
flags = addOtherFlags(flags, p.Check)
207+
if p.Check != nil {
208+
flags = addOtherFlags(flags, p.Check)
209+
}
199210
}
200211

201212
return flags
@@ -214,6 +225,9 @@ func (p *Profile) GetRetentionFlags() map[string][]string {
214225

215226
// GetBackupSource returns the directories to backup
216227
func (p *Profile) GetBackupSource() []string {
228+
if p.Backup == nil {
229+
return nil
230+
}
217231
return p.Backup.Source
218232
}
219233

examples/dev.conf

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ TMP= "/tmp"
2929
[root]
3030
inherit = "default"
3131
initialize = true
32+
run-before = "echo mount backup disk"
33+
run-after = ["echo sync", "echo umount backup disk"]
3234

3335
# 'backup' command of profile 'root'
3436
[root.backup]

examples/profiles.conf

+2
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ host = true
6969
[src]
7070
inherit = "default"
7171
initialize = true
72+
run-before = "echo mount backup disk"
73+
run-after = ["echo sync", "echo umount backup disk"]
7274

7375
# 'backup' command of profile 'src'
7476
[src.backup]

main.go

+13
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,12 @@ func runProfile(global *config.Global, flags commandLineFlags, profileName strin
244244
err = lockRun(profile.Lock, func() error {
245245
var err error
246246

247+
// pre-profile commands
248+
err = wrapper.runProfilePreCommand()
249+
if err != nil {
250+
return err
251+
}
252+
247253
// pre-commands (for backup)
248254
if resticCommand == constants.CommandBackup {
249255
// Shell commands
@@ -295,6 +301,13 @@ func runProfile(global *config.Global, flags commandLineFlags, profileName strin
295301
return err
296302
}
297303
}
304+
305+
// post-profile commands
306+
err = wrapper.runProfilePostCommand()
307+
if err != nil {
308+
return err
309+
}
310+
298311
return nil
299312
})
300313
if err != nil {

wrapper.go

+37-3
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func (r *resticWrapper) prepareCommand(command string, args []string) shellComma
7676
rCommand := newShellCommand(r.resticBinary, arguments, env)
7777
rCommand.sigChan = r.sigChan
7878

79-
if command == constants.CommandBackup && r.profile.Backup.UseStdin {
79+
if command == constants.CommandBackup && r.profile.Backup != nil && r.profile.Backup.UseStdin {
8080
clog.Debug("Redirecting stdin to the backup")
8181
rCommand.useStdin = true
8282
}
@@ -88,7 +88,7 @@ func (r *resticWrapper) runPreCommand(command string) error {
8888
if command != constants.CommandBackup {
8989
return nil
9090
}
91-
if r.profile.Backup.RunBefore == nil || len(r.profile.Backup.RunBefore) == 0 {
91+
if r.profile.Backup == nil || r.profile.Backup.RunBefore == nil || len(r.profile.Backup.RunBefore) == 0 {
9292
return nil
9393
}
9494
for i, preCommand := range r.profile.Backup.RunBefore {
@@ -109,7 +109,7 @@ func (r *resticWrapper) runPostCommand(command string) error {
109109
if command != constants.CommandBackup {
110110
return nil
111111
}
112-
if r.profile.Backup.RunAfter == nil || len(r.profile.Backup.RunAfter) == 0 {
112+
if r.profile.Backup == nil || r.profile.Backup.RunAfter == nil || len(r.profile.Backup.RunAfter) == 0 {
113113
return nil
114114
}
115115
for i, postCommand := range r.profile.Backup.RunAfter {
@@ -125,6 +125,40 @@ func (r *resticWrapper) runPostCommand(command string) error {
125125
return nil
126126
}
127127

128+
func (r *resticWrapper) runProfilePreCommand() error {
129+
if r.profile.RunBefore == nil || len(r.profile.RunBefore) == 0 {
130+
return nil
131+
}
132+
for i, preCommand := range r.profile.RunBefore {
133+
clog.Debugf("Starting 'run-before' profile command %d/%d", i+1, len(r.profile.RunBefore))
134+
env := append(os.Environ(), r.getEnvironment()...)
135+
rCommand := newShellCommand(preCommand, nil, env)
136+
rCommand.sigChan = r.sigChan
137+
err := runShellCommand(rCommand)
138+
if err != nil {
139+
return err
140+
}
141+
}
142+
return nil
143+
}
144+
145+
func (r *resticWrapper) runProfilePostCommand() error {
146+
if r.profile.RunAfter == nil || len(r.profile.RunAfter) == 0 {
147+
return nil
148+
}
149+
for i, postCommand := range r.profile.RunAfter {
150+
clog.Debugf("Starting 'run-after' profile command %d/%d", i+1, len(r.profile.RunAfter))
151+
env := append(os.Environ(), r.getEnvironment()...)
152+
rCommand := newShellCommand(postCommand, nil, env)
153+
rCommand.sigChan = r.sigChan
154+
err := runShellCommand(rCommand)
155+
if err != nil {
156+
return err
157+
}
158+
}
159+
return nil
160+
}
161+
128162
func (r *resticWrapper) getEnvironment() []string {
129163
if r.profile.Environment == nil || len(r.profile.Environment) == 0 {
130164
return nil

0 commit comments

Comments
 (0)