@@ -189,6 +189,9 @@ func (r *resticWrapper) runProfile() error {
189189 func (err error ) {
190190 _ = r .runProfilePostFailCommand (err )
191191 },
192+ func (err error ) {
193+ r .runFinalCommand (r .command , err )
194+ },
192195 )
193196 })
194197 if err != nil {
@@ -420,22 +423,7 @@ func (r *resticWrapper) runProfilePostFailCommand(fail error) error {
420423 }
421424 env := append (os .Environ (), r .getEnvironment ()... )
422425 env = append (env , r .getProfileEnvironment ()... )
423- env = append (env , fmt .Sprintf ("ERROR=%s" , fail .Error ()))
424-
425- if fail , ok := fail .(* commandError ); ok {
426- exitCode := - 1
427- if code , err := fail .ExitCode (); err == nil {
428- exitCode = code
429- }
430-
431- env = append (env ,
432- fmt .Sprintf ("ERROR_COMMANDLINE=%s" , fail .Commandline ()),
433- fmt .Sprintf ("ERROR_EXIT_CODE=%d" , exitCode ),
434- fmt .Sprintf ("ERROR_STDERR=%s" , fail .Stderr ()),
435- // Deprecated: STDERR can originate from (pre/post)-command which doesn't need to be restic
436- fmt .Sprintf ("RESTIC_STDERR=%s" , fail .Stderr ()),
437- )
438- }
426+ env = append (env , r .getFailEnvironment (fail )... )
439427
440428 for i , postCommand := range r .profile .RunAfterFail {
441429 clog .Debugf ("starting 'run-after-fail' profile command %d/%d" , i + 1 , len (r .profile .RunAfterFail ))
@@ -451,6 +439,37 @@ func (r *resticWrapper) runProfilePostFailCommand(fail error) error {
451439 return nil
452440}
453441
442+ func (r * resticWrapper ) runFinalCommand (command string , fail error ) {
443+ var commands []string
444+
445+ if command == constants .CommandBackup && r .profile .Backup != nil && r .profile .Backup .RunFinally != nil {
446+ commands = append (commands , r .profile .Backup .RunFinally ... )
447+ }
448+ if r .profile .RunFinally != nil {
449+ commands = append (commands , r .profile .RunFinally ... )
450+ }
451+
452+ env := append (os .Environ (), r .getEnvironment ()... )
453+ env = append (env , r .getProfileEnvironment ()... )
454+ env = append (env , r .getFailEnvironment (fail )... )
455+
456+ for i := len (commands ) - 1 ; i >= 0 ; i -- {
457+ // Using defer stack for "finally" to ensure every command is run even on panic
458+ defer func (index int , cmd string ) {
459+ clog .Debugf ("starting final command %d/%d" , index + 1 , len (commands ))
460+ rCommand := newShellCommand (cmd , nil , env , r .dryRun , r .sigChan , r .setPID )
461+ // stdout are stderr are coming from the default terminal (in case they're redirected)
462+ rCommand .stdout = term .GetOutput ()
463+ rCommand .stderr = term .GetErrorOutput ()
464+ _ , _ , err := runShellCommand (rCommand )
465+ if err != nil {
466+ clog .Errorf ("run-finally command %d/%d failed ('%s' on profile '%s'): %w" ,
467+ index + 1 , len (commands ), command , r .profile .Name , err )
468+ }
469+ }(i , commands [i ])
470+ }
471+ }
472+
454473// getEnvironment returns the environment variables defined in the profile configuration
455474func (r * resticWrapper ) getEnvironment () []string {
456475 if r .profile .Environment == nil || len (r .profile .Environment ) == 0 {
@@ -477,6 +496,31 @@ func (r *resticWrapper) getProfileEnvironment() []string {
477496 }
478497}
479498
499+ // getFailEnvironment returns additional environment variables describing the fail reason
500+ func (r * resticWrapper ) getFailEnvironment (err error ) (env []string ) {
501+ if err == nil {
502+ return
503+ }
504+
505+ env = []string {fmt .Sprintf ("ERROR=%s" , err .Error ())}
506+
507+ if fail , ok := err .(* commandError ); ok {
508+ exitCode := - 1
509+ if code , err := fail .ExitCode (); err == nil {
510+ exitCode = code
511+ }
512+
513+ env = append (env ,
514+ fmt .Sprintf ("ERROR_COMMANDLINE=%s" , fail .Commandline ()),
515+ fmt .Sprintf ("ERROR_EXIT_CODE=%d" , exitCode ),
516+ fmt .Sprintf ("ERROR_STDERR=%s" , fail .Stderr ()),
517+ // Deprecated: STDERR can originate from (pre/post)-command which doesn't need to be restic
518+ fmt .Sprintf ("RESTIC_STDERR=%s" , fail .Stderr ()),
519+ )
520+ }
521+ return
522+ }
523+
480524// canSucceedAfterError returns true if an error reported by running restic in runCommand can be counted as success
481525func (r * resticWrapper ) canSucceedAfterError (command string , summary progress.Summary , err error ) bool {
482526 if err == nil {
@@ -689,12 +733,20 @@ func logLockWait(lockName string, started, lastLogged time.Time, maxLockWait tim
689733}
690734
691735// runOnFailure will run the onFailure function if an error occurred in the run function
692- func runOnFailure (run func () error , onFailure func (error )) error {
693- err := run ()
736+ func runOnFailure (run func () error , onFailure func (error ), finally func (error )) (err error ) {
737+ // Using "defer" for finally to ensure it runs even on panic
738+ if finally != nil {
739+ defer func () {
740+ finally (err )
741+ }()
742+ }
743+
744+ err = run ()
694745 if err != nil {
695746 onFailure (err )
696747 }
697- return err
748+
749+ return
698750}
699751
700752func asExitError (err error ) (* exec.ExitError , bool ) {
0 commit comments