Custom Guix home service not working as intended

I have this guix home service for saving and loading brightness levels using brightnessctl,

(define-module (apoorv home services brightness)
  #:use-module (gnu home services)
  #:use-module (gnu home services shepherd)
  #:use-module (gnu packages)
  #:use-module (gnu packages linux)
  #:use-module (gnu services)
  #:use-module (gnu services configuration)
  #:use-module (guix gexp)

  #:export (home-brightnessctl-service-type))

(define (home-brightnessctl-profile-service config)
  (map specification->package
       (list "brightnessctl")))

(define (home-brightnessctl-shepherd-service config)
  (list
   (shepherd-service
    (provision '(brightnessctl))
    (documentation "Save and restore brightness.")
    (one-shot? #t)
    (start #~(lambda rest
               (fork+exec-command
                (list #$(file-append brightnessctl "/bin/brightnessctl") "--restore"))
               #t))
    (stop #~(lambda rest
              (fork+exec-command
               (list #$(file-append brightnessctl "/bin/brightnessctl") "--save"))
              #f)))))

(define home-brightnessctl-service-type
  (service-type
   (name 'home-brightnessctl)
   (description "A service for saving and restoring brightness.")
   (extensions
    (list (service-extension
           home-profile-service-type
           home-brightnessctl-profile-service)
          (service-extension
           home-shepherd-service-type
           home-brightnessctl-shepherd-service)))
   (default-value #f)))

This does nothing when I reboot the system for example, but if I lower the brightness then manually stop, raise or lower brightness and start it again, it does work as expected i.e it restores the brightness to the level I set it to before stopping the service.

What could be causing it not working on system reboot?

As I understand it because you specify (one-shot? #t) the service will run the start method and as soon as it’s done running will consider the service status to be “stopped”. I’m guessing you want the stop method to run when you reboot, but since shepherd already thinks that the service is stopped, I expect it won’t do anything.

This is what I understand after reading these sections:

I see.

What situations would one-shot? be useful in? The reason I use it because I thought, there is no point of keep this service running in background.

EDIT: Just tried removing one-shot?, rebooted couple of times, no luck. Still same, upon reboot the brightness is always at 100%.

I have a service or two that just need to run when I log in. For example running xmodmap to change the buttons on my mouse, mpd-update that updates my mpd database, and things like that. They just need to run once, and they don’t need to keep running in the background.

It’s always 100% even when you change the brightness and manually call herd stop brightnessctl before rebooting?

Does your shepherd log file mention anything about brightnessctl? I’m not sure if this is standard, but mine is in ~/.local/state/log/shepherd.log

I have a service or two that just need to run when I log in. For example running xmodmap to change the buttons on my mouse, mpd-update that updates my mpd database, and things like that. They just need to run once, and they don’t need to keep running in the background.

I see, would you say for running emacs --daemon I should do one-shot? ? Because without one-shot? it for some reason starts multiple instances of the daemon.

It’s always 100% even when you change the brightness and manually call herd stop brightnessctl before rebooting?

Tried manually stop the brightnessctl service, then rebooted. Still same.

Does your shepherd log file mention anything about brightnessctl? I’m not sure if this is standard, but mine is in ~/.local/state/log/shepherd.log

Hmm, looking in the shepherd.log file, I found this,

2024-03-13 03:40:54 [brightnessctl] Error restoring device data: No such file or directory
2024-03-13 03:40:54 [brightnessctl] Device 'amdgpu_bl0' of class 'backlight':
2024-03-13 03:40:54 [brightnessctl] 	Current brightness: 255 (100%)
2024-03-13 03:40:54 [brightnessctl] 	Max brightness: 255
2024-03-13 03:40:54 [brightnessctl] 
2024-03-13 03:40:54 Service brightnessctl started.

also looking at brightnessctl --help, the --save option says this,

  -s, --save            save previous state in a temporary file.

maybe it saves to a file somewhere, where the file doesn’t exist upon reboot?

EDIT: Looking around, I found that brightnessctl --save saves it under,

/run/user/1000/brightnessctl/

in my case its specifically,

/run/user/1000/brightnessctl/backlight/amdgpu_bl0

I think /run/user/1000 directory doesn’t persist upon reboot, hence the error while trying to restore.

I opened an issue on their repo, How to restore last set brightness after reboot? · Issue #104 · Hummer12007/brightnessctl · GitHub

1 Like

No, the Emacs daemon would definitely not be one-shot, because it keeps running in the background.

It’s odd that it’s starting multiple instances, what’s your service look like and does your log say anything? It’s also odd because unless you give them different names usually you can’t have multiple instances of the Emacs daemon.

Oh! Actually… it might have to do with using emacs --daemon? Since that starts a background process, so it forks immediately and the command returns right away. This might cause shepherd to try and respawn the service, causing multiple instances to start.

I was just looking at my own shepherd service, and I use --fg-daemon so that it never forks and shepherd can tell that it’s still running.

(define (home-emacs-shepherd-service config)
  (list
   (shepherd-service
    (documentation "Start Emacs")
    (provision '(emacs))
    (auto-start? #t)
    (start
     #~(make-forkexec-constructor
        (list #$(file-append (home-emacs-configuration-package config) "/bin/emacs")
              "--fg-daemon")
        #:log-file (format #f "~a/.local/var/log/xbindkeys.log" (getenv "HOME"))))
    (stop #~(make-kill-destructor)))))

home-emacs-configuration-package is the package of my Emacs configuration object, it ends up being the Emacs package that I use (emacs on some emacs-next-tree-sitter on others).

Maybe this will help you with yours?

Oh well this makes a lot more sense then, since yeah like you said the /run/user/1000 directory doesn’t persist across reboots. Good luck with the issue! I’m not 100% sure what the use is for the temporary file.

In the mean time, you could always do something along the lines of:

# To save
brightnessctl get > ~/.local/state/brightness/value
# To load
brightnessctl set $(cat ~/.local/state/brightness/value)

Instead of using --save and --restore.

Oh! Actually… it might have to do with using emacs --daemon? Since that starts a background process, so it forks immediately and the command returns right away. This might cause shepherd to try and respawn the service, causing multiple instances to start.

I see.

I was just looking at my own shepherd service, and I use --fg-daemon so that it never forks and shepherd can tell that it’s still running.

Oh!, I didn’t know about the --fg-daemon option. This might help.

Oh well this makes a lot more sense then, since yeah like you said the /run/user/1000 directory doesn’t persist across reboots. Good luck with the issue! I’m not 100% sure what the use is for the temporary file.

Yea, not sure why those options exists though.

In the mean time, you could always do something along the lines of:

# To save
brightnessctl get > ~/.local/state/brightness/value
# To load
brightnessctl set $(cat ~/.local/state/brightness/value)

Instead of using --save and --restore.

Good idea. This should work.

1 Like

OK, so I made these changes,

(define (home-brightnessctl-shepherd-service config)
  (list
   (shepherd-service
    (provision '(brightnessctl))
    (documentation "Save and restore brightness.")
    ;; (one-shot? #t)
    (start #~(lambda rest
               (fork+exec-command
                ;; (list #$(file-append brightnessctl "/bin/brightnessctl") "--restore"))
                '("brightnessctl" "set" "$(cat" #$(string-append (getenv "HOME") "/.local/state/brightnessctl)")))
               #t))
    (stop #~(lambda rest
              (fork+exec-command
               ;; (list #$(file-append brightnessctl "/bin/brightnessctl") "--save"))
               '("brightnessctl" "get" ">" #$(string-append (getenv "HOME") "/.local/state/brightnessctl")))
              #f)))))

I don’t see any file being created called brightnessctl under ~/.local/state/

there are no errors either, here are the logs,

2024-03-14 05:17:34 Stopping service brightnessctl...
2024-03-14 05:17:34 Service brightnessctl stopped.
2024-03-14 05:17:34 Service brightnessctl is now stopped.
2024-03-14 05:17:34 [brightnessctl] 115
2024-03-14 05:19:04 Starting service brightnessctl...
2024-03-14 05:19:04 Service brightnessctl started.
2024-03-14 05:19:04 Service brightnessctl running with value #t.
2024-03-14 05:19:04 Service brightnessctl has been started.

I’m not sure if fork+exec-command starts a shell or not, although from the log it appears like it may not. I’ve noticed that brightnessctl get will ignore any extra arguments that it gets and just print the value regardless. So it appears that it’s a bit like it’s executing brightnessctl get '>' '~/.local/state/brightnessctl', which just prints the current brightness.

I’m not sure what the point of exec-command is specifically, but I’ve tried this and from my very limited testing it seems to create the file and do everything you need:

(define (home-brightnessctl-shepherd-service config)
  (list
   (shepherd-service
    (provision '(brightnessctl))
    (documentation "Save and restore brightness.")
    (start #~(lambda rest
               (system #$(format #f "brightnessctl set $(cat ~a/.local/share/brightnessctl) &" (getenv "HOME")))
               #t))
    (stop #~(lambda rest
              (system #$(format #f "brightnessctl get > ~a/.local/share/brightnessctl &" (getenv "HOME")))
              #f)))))

So I’m not sure if there is a downside to running system in a service, but like I said it seems to work.

I added the & so they run in the background like they did before, otherwise there is the potential that if it takes a long time to execute (your disk is busy or something) it shouldn’t block other services from starting / stopping.

1 Like

This seems to work. I guess I can mark this one as solved. Thank @ryuslash.

1 Like