Skip to content

Commit

Permalink
full implementation of systemd user unit
Browse files Browse the repository at this point in the history
  • Loading branch information
creativeprojects committed Jul 3, 2020
1 parent 392b1a9 commit c73ccb3
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 91 deletions.
35 changes: 20 additions & 15 deletions commands.go
Expand Up @@ -11,7 +11,6 @@ import (
"github.com/creativeprojects/resticprofile/config"
"github.com/creativeprojects/resticprofile/constants"
"github.com/creativeprojects/resticprofile/schedule"
"github.com/creativeprojects/resticprofile/systemd"
)

type ownCommand struct {
Expand All @@ -36,12 +35,6 @@ var (
action: selfUpdate,
needConfiguration: false,
},
{
name: "systemd-unit",
description: "create a user systemd timer",
action: createSystemdTimer,
needConfiguration: true,
},
{
name: "show",
description: "show all the details of the current profile",
Expand Down Expand Up @@ -70,6 +63,13 @@ var (
needConfiguration: true,
hide: false,
},
{
name: "status",
description: "display the status of a scheduled backup job",
action: statusSchedule,
needConfiguration: true,
hide: false,
},
}
)

Expand Down Expand Up @@ -152,14 +152,6 @@ func selfUpdate(_ *config.Config, flags commandLineFlags, args []string) error {
return nil
}

func createSystemdTimer(_ *config.Config, flags commandLineFlags, args []string) error {
if len(args) != 1 {
return fmt.Errorf("OnCalendar argument required")
}
systemd.Generate(flags.name, args[0])
return nil
}

func panicCommand(_ *config.Config, _ commandLineFlags, _ []string) error {
panic("you asked for it")
}
Expand Down Expand Up @@ -219,3 +211,16 @@ func removeSchedule(c *config.Config, flags commandLineFlags, args []string) err
}
return err
}

func statusSchedule(c *config.Config, flags commandLineFlags, args []string) error {
profile, err := c.GetProfile(flags.name)
if err != nil {
return fmt.Errorf("cannot load profile '%s': %w", flags.name, err)
}
if profile == nil {
return fmt.Errorf("profile '%s' not found", flags.name)
}

job := schedule.NewJob(flags.config, profile)
return job.Status()
}
1 change: 1 addition & 0 deletions examples/linux.yaml
Expand Up @@ -11,6 +11,7 @@ default:

documents:
inherit: default
initialize: true
schedule: "*:0,15,30,45" # every 15 minutes
backup:
source: ~/Documents
Expand Down
10 changes: 10 additions & 0 deletions schedule/schedule.go
Expand Up @@ -11,6 +11,7 @@ import (

// Job scheduler
type Job struct {
binary string
configFile string
profile *config.Profile
schedules []*calendar.Event
Expand All @@ -30,6 +31,10 @@ func (j *Job) Create() error {
if err != nil {
return err
}
err = j.createJob()
if err != nil {
return err
}
return nil
}

Expand All @@ -43,6 +48,11 @@ func (j *Job) Remove() error {
return nil
}

// Status of a job
func (j *Job) Status() error {
return j.displayStatus()
}

func (j *Job) checkSchedules() error {
var err error
if j.profile.Schedule == nil || len(j.profile.Schedule) == 0 {
Expand Down
17 changes: 10 additions & 7 deletions schedule/schedule_darwin.go
Expand Up @@ -14,7 +14,6 @@ import (
"strings"

"github.com/creativeprojects/resticprofile/calendar"
"github.com/creativeprojects/resticprofile/config"
"howett.net/plist"
)

Expand Down Expand Up @@ -51,8 +50,8 @@ type CalendarInterval struct {
Minute int `plist:"Minute,omitempty"` // Minute of hour (0..59)
}

// CreateJob creates a plist file and register it with launchd
func CreateJob(configFile string, profile *config.Profile) error {
// createJob creates a plist file and register it with launchd
func (j *Job) createJob() error {
wd, err := os.Getwd()
if err != nil {
return err
Expand All @@ -64,20 +63,20 @@ func CreateJob(configFile string, profile *config.Profile) error {

binary := absolutePathToBinary(wd, os.Args[0])

name := namePrefix + strings.ToLower(profile.Name)
name := namePrefix + strings.ToLower(j.profile.Name)
job := &LaunchJob{
Label: name,
Program: binary,
ProgramArguments: []string{
binary,
"--no-ansi",
"--config",
configFile,
j.configFile,
"--name",
profile.Name,
j.profile.Name,
"backup",
},
EnvironmentVariables: profile.Environment,
EnvironmentVariables: j.profile.Environment,
StandardOutPath: name + ".log",
StandardErrorPath: name + ".error.log",
WorkingDirectory: wd,
Expand Down Expand Up @@ -117,6 +116,10 @@ func RemoveJob(profileName string) error {
return os.Remove(path.Join(home, UserAgentPath, name+agentExtension))
}

func (j *Job) displayStatus() error {
return nil
}

func loadSchedules(schedules []string) ([]*calendar.Event, error) {
events := make([]*calendar.Event, 0, len(schedules))
for index, schedule := range schedules {
Expand Down
105 changes: 100 additions & 5 deletions schedule/schedule_systemd.go
Expand Up @@ -7,16 +7,48 @@ import (
"fmt"
"os"
"os/exec"
"path"

"github.com/creativeprojects/resticprofile/calendar"
"github.com/creativeprojects/resticprofile/config"
"github.com/creativeprojects/resticprofile/systemd"
"github.com/creativeprojects/resticprofile/ui"
)

func CreateJob(configFile string, profile *config.Profile) error {
return nil
}

func RemoveJob(profileName string) error {
// stop the job
cmd := exec.Command("systemctl", "--user", "stop", systemd.GetTimerFile(profileName))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return err
}

// disable the job
cmd = exec.Command("systemctl", "--user", "disable", systemd.GetTimerFile(profileName))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return err
}

systemdUserDir, err := systemd.GetUserDir()
if err != nil {
return nil
}
timerFile := systemd.GetTimerFile(profileName)
err = os.Remove(path.Join(systemdUserDir, timerFile))
if err != nil {
return nil
}

serviceFile := systemd.GetServiceFile(profileName)
err = os.Remove(path.Join(systemdUserDir, serviceFile))
if err != nil {
return nil
}

return nil
}

Expand All @@ -37,3 +69,66 @@ func loadSchedules(schedules []string) ([]*calendar.Event, error) {
}
return events, nil
}

// createJob is creating the systemd unit and activating it.
// for systemd the schedules parameter is not used.
func (j *Job) createJob() error {
if os.Geteuid() == 0 {
// user has sudoed already
return j.createSystemJob()
}
message := "\nPlease note resticprofile was started as a standard user (typically without sudo):" +
"\nDo you want to install the scheduled backup as a user job as opposed to a system job?"
answer := ui.AskYesNo(os.Stdin, message, false)
if !answer {
return errors.New("operation cancelled")
}
return j.createUserJob()
}

func (j *Job) createSystemJob() error {
return nil
}

func (j *Job) createUserJob() error {
wd, err := os.Getwd()
if err != nil {
return err
}
binary := absolutePathToBinary(wd, os.Args[0])
err = systemd.Generate(wd, binary, j.configFile, j.profile.Name, j.profile.Schedule)
if err != nil {
return err
}

// enable the job
cmd := exec.Command("systemctl", "--user", "enable", systemd.GetTimerFile(j.profile.Name))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return err
}

// start the job
cmd = exec.Command("systemctl", "--user", "start", systemd.GetTimerFile(j.profile.Name))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return err
}

return nil
}

func (j *Job) displayStatus() error {
cmd := exec.Command("systemctl", "--user", "status", systemd.GetTimerFile(j.profile.Name))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return err
}
return nil
}
14 changes: 9 additions & 5 deletions schedule/schedule_windows.go
Expand Up @@ -9,14 +9,14 @@ import (

"github.com/capnspacehook/taskmaster"
"github.com/creativeprojects/resticprofile/calendar"
"github.com/creativeprojects/resticprofile/config"
)

const (
tasksPath = `\resticprofile backup\`
)

func CreateJob(configFile string, profile *config.Profile) error {
// createJob is creating the task manager job.
func (j *Job) createJob() error {
wd, err := os.Getwd()
if err != nil {
return err
Expand All @@ -37,12 +37,12 @@ func CreateJob(configFile string, profile *config.Profile) error {
binary := absolutePathToBinary(wd, os.Args[0])

task := taskService.NewTaskDefinition()
task.AddExecAction(binary, fmt.Sprintf("--no-ansi --config %s --name %s backup", configFile, profile.Name), wd, "")
task.AddExecAction(binary, fmt.Sprintf("--no-ansi --config %s --name %s backup", j.configFile, j.profile.Name), wd, "")
task.Principal.LogonType = taskmaster.TASK_LOGON_SERVICE_ACCOUNT
task.Principal.RunLevel = taskmaster.TASK_RUNLEVEL_HIGHEST
task.Principal.UserID = "SYSTEM"
task.RegistrationInfo.Description = fmt.Sprintf("restic backup using profile '%s' from '%s'", profile.Name, configFile)
_, _, err = taskService.CreateTask(tasksPath+profile.Name, task, true)
task.RegistrationInfo.Description = fmt.Sprintf("restic backup using profile '%s' from '%s'", j.profile.Name, j.configFile)
_, _, err = taskService.CreateTask(tasksPath+j.profile.Name, task, true)
if err != nil {
return err
}
Expand All @@ -59,6 +59,10 @@ func RemoveJob(profileName string) error {
return taskService.DeleteTask(tasksPath + profileName)
}

func (j *Job) displayStatus() error {
return nil
}

func loadSchedules(schedules []string) ([]*calendar.Event, error) {
events := make([]*calendar.Event, 0, len(schedules))
for index, schedule := range schedules {
Expand Down

0 comments on commit c73ccb3

Please sign in to comment.