Skip to content

Commit e490c42

Browse files
Set schedule permissions (#476)
* create user owned service and timer when scheduled using sudo * fix HOME on user permission * change owner of drop ins files when user scheduling with sudo * remove schedule with different permission * don't need to check for error at this stage
1 parent ac3b810 commit e490c42

13 files changed

Lines changed: 272 additions & 175 deletions

File tree

.github/workflows/doc.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
with:
5656
url: "https://${{ env.BRANCH_NAME }}.resticprofile.pages.dev/"
5757
pages_path: ./public/
58-
cmd_params: '--exclude="(linux\.die\.net|stackoverflow\.com|scoop\.sh|0-18)" --buffer-size=8192 --max-connections=10 --color=always --skip-tls-verification --header="User-Agent:curl/7.54.0" --timeout=20'
58+
cmd_params: '--exclude="(linux\.die\.net|scoop\.sh|0-18)" --buffer-size=8192 --max-connections-per-host=8 --color=always --skip-tls-verification --header="User-Agent: Muffet/2.10.8" --timeout=20'
5959

6060
- name: Publish to pages.dev
6161
continue-on-error: true # secrets are not set for PRs from forks

.github/workflows/release-doc.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ jobs:
4949
with:
5050
url: https://creativeprojects.github.io/resticprofile/
5151
pages_path: ./www/
52-
cmd_params: '--exclude="(linux\.die\.net|stackoverflow\.com|scoop\.sh|0-18)" --buffer-size=8192 --max-connections-per-host=5 --rate-limit=5 --timeout=20 --header="User-Agent:curl/7.54.0" --skip-tls-verification'
52+
cmd_params: '--exclude="(linux\.die\.net|scoop\.sh|0-18)" --buffer-size=8192 --max-connections-per-host=8 --timeout=20 --header="User-Agent: Muffet/2.10.8" --skip-tls-verification'
5353

5454
- name: Deploy to GitHub Pages
5555
uses: peaceiris/actions-gh-pages@v4

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,10 @@ checkdoc:
298298
.PHONY: checklinks
299299
checklinks:
300300
@echo "[*] $@"
301-
muffet -b 8192 --max-connections=10 --exclude="(linux\.die\.net|stackoverflow\.com|scoop\.sh|0-18)" http://localhost:1313/resticprofile/
301+
muffet -b 8192 --max-connections-per-host=8 \
302+
--exclude="(linux\.die\.net|scoop\.sh|0-18)" \
303+
--header="User-Agent: Muffet/$$(muffet --version)" \
304+
http://localhost:1313/resticprofile/
302305

303306
.PHONY: lint
304307
lint: $(GOBIN)/golangci-lint

examples/linux.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,14 @@ longrun:
9898
self:
9999
inherit: default
100100
status-file: /tmp/status.json
101+
# systemd-drop-in-files: [ drop-in-example.conf ]
101102
backup:
102103
extended-status: true
103104
source: ./
104105
schedule:
105106
at: "*:15,20,25"
106107
permission: system
108+
after-network-online: true
107109
check:
108110
schedule-permission: user
109111
schedule:

schedule/handler_crond.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ func (h *HandlerCrond) CheckPermission(user user.User, p Permission) bool {
202202
return true
203203

204204
default:
205-
if user.SudoRoot || user.Uid == 0 {
205+
if user.IsRoot() {
206206
return true
207207
}
208208
// last case is system (or undefined) + no sudo

schedule/handler_darwin.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,7 @@ func (h *HandlerLaunchd) CheckPermission(user user.User, p Permission) bool {
268268
return true
269269

270270
default:
271-
if user.SudoRoot || user.Uid == 0 {
272-
// user has sudoed
271+
if user.IsRoot() {
273272
return true
274273
}
275274
// last case is system (or undefined) + no sudo

schedule/handler_systemd.go

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,33 @@ func (h *HandlerSystemd) DisplayStatus(profileName string) error {
108108

109109
// CreateJob is creating the systemd unit and activating it
110110
func (h *HandlerSystemd) CreateJob(job *Config, schedules []*calendar.Event, permission Permission) error {
111-
unitType, user := permissionToSystemd(user.Current(), permission)
111+
u := user.Current()
112+
unitType, user := permissionToSystemd(u, permission)
112113

113114
if unitType == systemd.UserUnit && job.AfterNetworkOnline {
114115
return fmt.Errorf("after-network-online is not available for \"user_logged_on\" permission schedules")
115116
}
116117

117-
err := systemd.Generate(systemd.Config{
118+
// check the user hasn't changed the permission, which could duplicate the unit (system & user)
119+
otherUnitType := systemd.SystemUnit
120+
if unitType == systemd.SystemUnit {
121+
otherUnitType = systemd.UserUnit
122+
}
123+
if cfgs, _ := getConfigs(job.ProfileName, otherUnitType); len(cfgs) > 0 { // ignore errors here
124+
for _, cfg := range cfgs {
125+
if cfg.CommandName == job.CommandName && cfg.ProfileName == job.ProfileName {
126+
// we'd better remove this schedule first
127+
clog.Infof("removing existing unit with different permission")
128+
err := h.RemoveJob(&cfg, PermissionFromConfig(cfg.Permission))
129+
if err != nil {
130+
return fmt.Errorf("cannot remove existing unit before scheduling with different permission. You might want to retry using sudo.")
131+
}
132+
}
133+
}
134+
}
135+
136+
unit := systemd.NewUnit(u)
137+
err := unit.Generate(systemd.Config{
118138
CommandLine: job.Command + " --no-prio " + job.Arguments.String(),
119139
Environment: job.Environment,
120140
WorkingDirectory: job.WorkingDirectory,
@@ -169,7 +189,9 @@ func (h *HandlerSystemd) CreateJob(job *Config, schedules []*calendar.Event, per
169189
// RemoveJob is disabling the systemd unit and deleting the timer and service files
170190
func (h *HandlerSystemd) RemoveJob(job *Config, permission Permission) error {
171191
var err error
172-
unitType, _ := permissionToSystemd(user.Current(), permission)
192+
unit := systemd.NewUnit(user.Current())
193+
u := user.Current()
194+
unitType, _ := permissionToSystemd(u, permission)
173195
serviceFile := systemd.GetServiceFile(job.ProfileName, job.CommandName)
174196
unitLoaded, err := unitLoaded(serviceFile, unitType)
175197
if err != nil {
@@ -195,7 +217,7 @@ func (h *HandlerSystemd) RemoveJob(job *Config, permission Permission) error {
195217

196218
systemdPath := systemd.GetSystemDir()
197219
if unitType == systemd.UserUnit {
198-
systemdPath, err = systemd.GetUserDir()
220+
systemdPath, err = unit.GetUserDir()
199221
if err != nil {
200222
return nil
201223
}
@@ -241,7 +263,12 @@ func (h *HandlerSystemd) DisplayJobStatus(job *Config) error {
241263
if !unitLoaded {
242264
return ErrScheduledJobNotFound
243265
}
244-
_ = runJournalCtlCommand(timerName, systemdType) // ignore errors on journalctl
266+
if systemdType == systemd.UserUnit && user.Current().IsRoot() {
267+
// journalctl doesn't accept the parameter "-M user@" (yet?)
268+
clog.Warning("cannot load the journal from a user service as root")
269+
} else {
270+
_ = runJournalCtlCommand(timerName, systemdType) // ignore errors on journalctl
271+
}
245272
return runSystemctlCommand(timerName, systemctlStatus, systemdType, false)
246273
}
247274

@@ -294,7 +321,7 @@ func (h *HandlerSystemd) CheckPermission(user user.User, p Permission) bool {
294321
return true
295322

296323
default:
297-
if user.SudoRoot || user.Uid == 0 {
324+
if user.IsRoot() {
298325
return true
299326
}
300327
// last case is system (or undefined) + no sudo
@@ -476,7 +503,7 @@ func getConfigs(profileName string, unitType systemd.UnitType) ([]Config, error)
476503
if unit.Load == unitNotFound {
477504
continue
478505
}
479-
cfg, err := systemd.Read(unit.Unit, unitType)
506+
cfg, err := systemd.NewUnit(user.Current()).Read(unit.Unit, unitType)
480507
if err != nil {
481508
clog.Errorf("cannot read information from unit %q: %s", unit.Unit, err)
482509
continue

systemd/drop_ins.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ func getOwnedName(basename string) string {
2323
return fmt.Sprintf("%s.resticprofile.conf", strings.TrimSuffix(basename, ext))
2424
}
2525

26-
func IsTimerDropIn(file string) bool {
27-
if f, err := fs.Open(file); err == nil {
26+
func (u Unit) IsTimerDropIn(file string) bool {
27+
if f, err := u.fs.Open(file); err == nil {
2828
defer func() { _ = f.Close() }()
2929
for reader := bufio.NewScanner(f); reader.Scan(); {
3030
if timerDropInRegex.Match(reader.Bytes()) {
@@ -37,8 +37,8 @@ func IsTimerDropIn(file string) bool {
3737
return false
3838
}
3939

40-
func CreateDropIns(dir string, files []string) error {
41-
if err := fs.MkdirAll(dir, 0o755); err != nil {
40+
func (u Unit) createDropIns(dir string, files []string) error {
41+
if err := u.fs.MkdirAll(dir, 0o755); err != nil {
4242
return err
4343
}
4444

@@ -47,7 +47,7 @@ func CreateDropIns(dir string, files []string) error {
4747
fileBasenamesOwned[getOwnedName(filepath.Base(file))] = struct{}{}
4848
}
4949

50-
d, err := fs.Open(dir)
50+
d, err := u.fs.Open(dir)
5151
if err != nil {
5252
return err
5353
}
@@ -66,7 +66,7 @@ func CreateDropIns(dir string, files []string) error {
6666
if createdByUs && !notOrphaned {
6767
orphanPath := filepath.Join(dir, f.Name())
6868
clog.Debugf("deleting orphaned drop-in file %v", orphanPath)
69-
if err := fs.Remove(orphanPath); err != nil {
69+
if err := u.fs.Remove(orphanPath); err != nil {
7070
return err
7171
}
7272
}
@@ -79,13 +79,13 @@ func CreateDropIns(dir string, files []string) error {
7979
dropInFileOwned := getOwnedName(dropInFileBase)
8080
dstPath := filepath.Join(dir, dropInFileOwned)
8181
clog.Debugf("writing %v", dstPath)
82-
dst, err := fs.Create(dstPath)
82+
dst, err := u.fs.Create(dstPath)
8383
if err != nil {
8484
return err
8585
}
8686
defer dst.Close()
8787

88-
src, err := fs.Open(dropInFilePath)
88+
src, err := u.fs.Open(dropInFilePath)
8989
if err != nil {
9090
return err
9191
}

0 commit comments

Comments
 (0)