99 "os/user"
1010 "path/filepath"
1111 "text/template"
12+ "time"
1213
1314 "github.com/gookit/color"
1415)
@@ -26,8 +27,57 @@ func NewLinuxDaemonInstaller(baseFolder, user string) *LinuxDaemonInstaller {
2627 return & LinuxDaemonInstaller {baseFolder : baseFolder , user : user }
2728}
2829
30+ // getXDGRuntimeDir returns the XDG_RUNTIME_DIR path for the current user
31+ func (l * LinuxDaemonInstaller ) getXDGRuntimeDir () string {
32+ // First check if it's already set in environment
33+ if dir := os .Getenv ("XDG_RUNTIME_DIR" ); dir != "" {
34+ return dir
35+ }
36+ // Fall back to standard path
37+ return fmt .Sprintf ("/run/user/%d" , os .Getuid ())
38+ }
39+
40+ // ensureUserSystemdSession ensures the user's systemd session is available
41+ // by checking for XDG_RUNTIME_DIR and enabling linger if necessary
42+ func (l * LinuxDaemonInstaller ) ensureUserSystemdSession () error {
43+ runtimeDir := l .getXDGRuntimeDir ()
44+
45+ // Check if runtime directory exists
46+ if _ , err := os .Stat (runtimeDir ); err == nil {
47+ return nil // Directory exists, we're good
48+ }
49+
50+ // Try to enable linger to start user systemd session
51+ color .Yellow .Println ("🔧 Enabling user session persistence (loginctl enable-linger)..." )
52+ cmd := exec .Command ("loginctl" , "enable-linger" , l .user )
53+ if err := cmd .Run (); err != nil {
54+ return fmt .Errorf ("failed to enable linger for user %s: %w. Please run: sudo loginctl enable-linger %s" , l .user , err , l .user )
55+ }
56+
57+ // Wait for runtime directory to be created (up to 5 seconds)
58+ for i := 0 ; i < 10 ; i ++ {
59+ time .Sleep (500 * time .Millisecond )
60+ if _ , err := os .Stat (runtimeDir ); err == nil {
61+ return nil
62+ }
63+ }
64+
65+ return fmt .Errorf ("XDG_RUNTIME_DIR (%s) not available. Please log in interactively or run: sudo loginctl enable-linger %s" , runtimeDir , l .user )
66+ }
67+
68+ // systemctlUserCmd creates an exec.Cmd for systemctl --user with proper environment
69+ func (l * LinuxDaemonInstaller ) systemctlUserCmd (args ... string ) * exec.Cmd {
70+ fullArgs := append ([]string {"--user" }, args ... )
71+ cmd := exec .Command ("systemctl" , fullArgs ... )
72+
73+ // Set up environment with XDG_RUNTIME_DIR
74+ cmd .Env = append (os .Environ (), fmt .Sprintf ("XDG_RUNTIME_DIR=%s" , l .getXDGRuntimeDir ()))
75+
76+ return cmd
77+ }
78+
2979func (l * LinuxDaemonInstaller ) Check () error {
30- cmd := exec . Command ( "systemctl" , "--user" , "is-active" , "shelltime" )
80+ cmd := l . systemctlUserCmd ( "is-active" , "shelltime" )
3181 if err := cmd .Run (); err == nil {
3282 return nil
3383 }
@@ -44,11 +94,11 @@ func (l *LinuxDaemonInstaller) CheckAndStopExistingService() error {
4494 return fmt .Errorf ("failed to get current user: %w" , err )
4595 }
4696 servicePath := filepath .Join (currentUser .HomeDir , ".config/systemd/user/shelltime.service" )
47- if err := exec . Command ( "systemctl" , "--user" , "stop" , "shelltime" ).Run (); err != nil {
97+ if err := l . systemctlUserCmd ( "stop" , "shelltime" ).Run (); err != nil {
4898 return fmt .Errorf ("failed to stop existing service: %w" , err )
4999 }
50100 // Also disable to clean up
51- _ = exec . Command ( "systemctl" , "--user" , "disable" , "shelltime" ).Run ()
101+ _ = l . systemctlUserCmd ( "disable" , "shelltime" ).Run ()
52102 // Remove old symlink if exists
53103 _ = os .Remove (servicePath )
54104 }
@@ -111,18 +161,23 @@ func (l *LinuxDaemonInstaller) RegisterService() error {
111161}
112162
113163func (l * LinuxDaemonInstaller ) StartService () error {
164+ // Ensure user systemd session is available
165+ if err := l .ensureUserSystemdSession (); err != nil {
166+ return err
167+ }
168+
114169 color .Yellow .Println ("🔄 Reloading systemd..." )
115- if err := exec . Command ( "systemctl" , "--user" , "daemon-reload" ).Run (); err != nil {
170+ if err := l . systemctlUserCmd ( "daemon-reload" ).Run (); err != nil {
116171 return fmt .Errorf ("failed to reload systemd: %w" , err )
117172 }
118173
119174 color .Yellow .Println ("✨ Enabling service..." )
120- if err := exec . Command ( "systemctl" , "--user" , "enable" , "shelltime" ).Run (); err != nil {
175+ if err := l . systemctlUserCmd ( "enable" , "shelltime" ).Run (); err != nil {
121176 return fmt .Errorf ("failed to enable service: %w" , err )
122177 }
123178
124179 color .Yellow .Println ("🚀 Starting service..." )
125- if err := exec . Command ( "systemctl" , "--user" , "start" , "shelltime" ).Run (); err != nil {
180+ if err := l . systemctlUserCmd ( "start" , "shelltime" ).Run (); err != nil {
126181 return fmt .Errorf ("failed to start service: %w" , err )
127182 }
128183 return nil
@@ -142,8 +197,8 @@ func (l *LinuxDaemonInstaller) UnregisterService() error {
142197
143198 color .Yellow .Println ("🛑 Stopping and disabling service if running..." )
144199 // Try to stop and disable the service
145- _ = exec . Command ( "systemctl" , "--user" , "stop" , "shelltime" ).Run ()
146- _ = exec . Command ( "systemctl" , "--user" , "disable" , "shelltime" ).Run ()
200+ _ = l . systemctlUserCmd ( "stop" , "shelltime" ).Run ()
201+ _ = l . systemctlUserCmd ( "disable" , "shelltime" ).Run ()
147202
148203 color .Yellow .Println ("🗑 Removing service files..." )
149204 // Remove symlink from systemd
@@ -152,7 +207,7 @@ func (l *LinuxDaemonInstaller) UnregisterService() error {
152207 }
153208
154209 color .Yellow .Println ("🔄 Reloading systemd..." )
155- if err := exec . Command ( "systemctl" , "--user" , "daemon-reload" ).Run (); err != nil {
210+ if err := l . systemctlUserCmd ( "daemon-reload" ).Run (); err != nil {
156211 return fmt .Errorf ("failed to reload systemd: %w" , err )
157212 }
158213
0 commit comments