-
Notifications
You must be signed in to change notification settings - Fork 0
feat(daemon): support user-scope systemd service installation on Linux #118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ import ( | |
| "fmt" | ||
| "os" | ||
| "os/exec" | ||
| "os/user" | ||
| "path/filepath" | ||
| "text/template" | ||
|
|
||
|
|
@@ -26,7 +27,7 @@ func NewLinuxDaemonInstaller(baseFolder, user string) *LinuxDaemonInstaller { | |
| } | ||
|
|
||
| func (l *LinuxDaemonInstaller) Check() error { | ||
| cmd := exec.Command("systemctl", "is-active", "shelltime") | ||
| cmd := exec.Command("systemctl", "--user", "is-active", "shelltime") | ||
| if err := cmd.Run(); err == nil { | ||
| return nil | ||
| } | ||
|
|
@@ -36,13 +37,20 @@ func (l *LinuxDaemonInstaller) Check() error { | |
| func (l *LinuxDaemonInstaller) CheckAndStopExistingService() error { | ||
| color.Yellow.Println("🔍 Checking if service is running...") | ||
|
|
||
| if err := l.Check(); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| color.Yellow.Println("🛑 Stopping existing service...") | ||
| if err := exec.Command("systemctl", "stop", "shelltime").Run(); err != nil { | ||
| return fmt.Errorf("failed to stop existing service: %w", err) | ||
| if err := l.Check(); err == nil { | ||
| color.Yellow.Println("🛑 Stopping existing service...") | ||
| currentUser, err := user.Current() | ||
| if err != nil { | ||
| return fmt.Errorf("failed to get current user: %w", err) | ||
| } | ||
| servicePath := filepath.Join(currentUser.HomeDir, ".config/systemd/user/shelltime.service") | ||
| if err := exec.Command("systemctl", "--user", "stop", "shelltime").Run(); err != nil { | ||
| return fmt.Errorf("failed to stop existing service: %w", err) | ||
| } | ||
| // Also disable to clean up | ||
| _ = exec.Command("systemctl", "--user", "disable", "shelltime").Run() | ||
| // Remove old symlink if exists | ||
| _ = os.Remove(servicePath) | ||
| } | ||
| return nil | ||
| } | ||
|
|
@@ -80,7 +88,19 @@ func (l *LinuxDaemonInstaller) RegisterService() error { | |
| if l.baseFolder == "" { | ||
| return fmt.Errorf("base folder is not set") | ||
| } | ||
| servicePath := "/etc/systemd/system/shelltime.service" | ||
|
|
||
| currentUser, err := user.Current() | ||
| if err != nil { | ||
| return fmt.Errorf("failed to get current user: %w", err) | ||
| } | ||
|
|
||
| // Create systemd user directory if it doesn't exist | ||
| systemdUserDir := filepath.Join(currentUser.HomeDir, ".config/systemd/user") | ||
|
Comment on lines
+92
to
+98
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is code duplication in |
||
| if err := os.MkdirAll(systemdUserDir, 0755); err != nil { | ||
| return fmt.Errorf("failed to create systemd user directory: %w", err) | ||
| } | ||
|
|
||
| servicePath := filepath.Join(systemdUserDir, "shelltime.service") | ||
| if _, err := os.Stat(servicePath); err != nil { | ||
| sourceFile := filepath.Join(l.baseFolder, "daemon/shelltime.service") | ||
| if err := os.Symlink(sourceFile, servicePath); err != nil { | ||
|
|
@@ -92,17 +112,17 @@ func (l *LinuxDaemonInstaller) RegisterService() error { | |
|
|
||
| func (l *LinuxDaemonInstaller) StartService() error { | ||
| color.Yellow.Println("🔄 Reloading systemd...") | ||
| if err := exec.Command("systemctl", "daemon-reload").Run(); err != nil { | ||
| if err := exec.Command("systemctl", "--user", "daemon-reload").Run(); err != nil { | ||
| return fmt.Errorf("failed to reload systemd: %w", err) | ||
| } | ||
|
|
||
| color.Yellow.Println("✨ Enabling service...") | ||
| if err := exec.Command("systemctl", "enable", "shelltime").Run(); err != nil { | ||
| if err := exec.Command("systemctl", "--user", "enable", "shelltime").Run(); err != nil { | ||
| return fmt.Errorf("failed to enable service: %w", err) | ||
| } | ||
|
|
||
| color.Yellow.Println("🚀 Starting service...") | ||
| if err := exec.Command("systemctl", "start", "shelltime").Run(); err != nil { | ||
| if err := exec.Command("systemctl", "--user", "start", "shelltime").Run(); err != nil { | ||
| return fmt.Errorf("failed to start service: %w", err) | ||
| } | ||
| return nil | ||
|
|
@@ -112,19 +132,27 @@ func (l *LinuxDaemonInstaller) UnregisterService() error { | |
| if l.baseFolder == "" { | ||
| return fmt.Errorf("base folder is not set") | ||
| } | ||
|
|
||
| currentUser, err := user.Current() | ||
| if err != nil { | ||
| return fmt.Errorf("failed to get current user: %w", err) | ||
| } | ||
|
|
||
| servicePath := filepath.Join(currentUser.HomeDir, ".config/systemd/user/shelltime.service") | ||
|
|
||
| color.Yellow.Println("🛑 Stopping and disabling service if running...") | ||
| // Try to stop and disable the service | ||
| _ = exec.Command("systemctl", "stop", "shelltime").Run() | ||
| _ = exec.Command("systemctl", "disable", "shelltime").Run() | ||
| _ = exec.Command("systemctl", "--user", "stop", "shelltime").Run() | ||
| _ = exec.Command("systemctl", "--user", "disable", "shelltime").Run() | ||
|
|
||
| color.Yellow.Println("🗑 Removing service files...") | ||
| // Remove symlink from systemd | ||
| if err := os.Remove("/etc/systemd/system/shelltime.service"); err != nil && !os.IsNotExist(err) { | ||
| if err := os.Remove(servicePath); err != nil && !os.IsNotExist(err) { | ||
| return fmt.Errorf("failed to remove systemd service symlink: %w", err) | ||
| } | ||
|
|
||
| color.Yellow.Println("🔄 Reloading systemd...") | ||
| if err := exec.Command("systemctl", "daemon-reload").Run(); err != nil { | ||
| if err := exec.Command("systemctl", "--user", "daemon-reload").Run(); err != nil { | ||
| return fmt.Errorf("failed to reload systemd: %w", err) | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error from
os.Removeis ignored here. This is inconsistent withUnregisterService(line 150), which correctly handles the error by checking foros.IsNotExist. For better robustness and consistency, you should handle the error here as well. This will prevent silent failures if the file removal fails for unexpected reasons, such as permission issues.