From 92d75729b1e6ea94f96448caaf846b7a001f6954 Mon Sep 17 00:00:00 2001 From: tzvonimir Date: Tue, 25 Mar 2025 20:14:53 +0100 Subject: [PATCH 1/9] feat: add s6 support --- daemon/daemon.go | 187 ++++++++++++++++++++++++++++++++++++++--- daemon/services/oda.s6 | 17 ++++ descriptor.bin | Bin 7858 -> 7836 bytes 3 files changed, 190 insertions(+), 14 deletions(-) create mode 100644 daemon/services/oda.s6 diff --git a/daemon/daemon.go b/daemon/daemon.go index 6d1cde6..cdfc0df 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -30,6 +30,18 @@ const ( ServicePermission = 0644 DirPermission = 0755 BaseCollectCommand = "collect" + + // S6 service related constants + S6UserServiceDir = ".s6/service" + S6RootServiceDir = "/etc/s6/service" + S6ServiceName = "oda" + S6ServiceRunFilename = "run" + S6ServiceDownFilename = "down" + S6ServiceFinishFilename = "finish" + S6ServiceLogDir = "log" + S6ServiceLogRunFilename = "run" + S6DaemonTemplateLocation = "services/oda.s6" + S6LogTemplateLocation = "services/oda.s6.log" ) // Embedding scripts directory @@ -73,6 +85,44 @@ func (d *Daemon) InstallDaemonConfiguration() error { return err } + // If we run S6, we need to create the service directory and log directory + if isS6Available() && d.config.Os == config.Linux { + serviceDir := filepath.Dir(filePath) + logDir := filepath.Join(serviceDir, S6ServiceLogDir) + + if err := util.Fs.MkdirAll(serviceDir, DirPermission); err != nil { + d.logger.Err(err).Msg("Failed to create S6 service directory") + return fmt.Errorf("failed to create S6 service directory: %w", err) + } + + if err := util.Fs.MkdirAll(logDir, DirPermission); err != nil { + d.logger.Err(err).Msg("Failed to create S6 log directory") + return fmt.Errorf("failed to create S6 log directory: %w", err) + } + + logRunPath := filepath.Join(logDir, S6ServiceRunFilename) + logTmpl, err := template.ParseFS(templateFS, S6LogTemplateLocation) + if err != nil { + d.logger.Err(err).Msg("Failed to parse S6 log template") + return err + } + + var logContent bytes.Buffer + logTmpConf := map[string]interface{}{ + "Home": d.config.HomeDir, + } + + if err := logTmpl.Execute(&logContent, logTmpConf); err != nil { + d.logger.Err(err).Msg("Failed to execute S6 log template") + return err + } + + if err := afero.WriteFile(util.Fs, logRunPath, logContent.Bytes(), 0755); err != nil { + d.logger.Err(err).Msg("Failed to write S6 log run file") + return fmt.Errorf("failed to write S6 log run file: %w", err) + } + } + tmpl, err := template.ParseFS(templateFS, templatePath) if err != nil { d.logger.Err(err).Msg("Failed to parse config template") @@ -185,9 +235,16 @@ func (d *Daemon) StartDaemon() error { switch d.config.Os { case config.Linux: - if err := startLinuxDaemon(d.config.IsRoot); err != nil { - d.logger.Err(err).Msg("Failed to start daemon service") - return err + if isS6Available() { + if err := startS6Daemon(d.config.HomeDir, d.config.IsRoot); err != nil { + d.logger.Err(err).Msg("Failed to start S6 daemon service") + return err + } + } else { + if err := startLinuxDaemon(d.config.IsRoot); err != nil { + d.logger.Err(err).Msg("Failed to start daemon service") + return err + } } case config.MacOS: if err := startMacOSDaemon(d.config.HomeDir, d.config.IsRoot); err != nil { @@ -210,9 +267,16 @@ func (d *Daemon) StopDaemon() error { switch d.config.Os { case config.Linux: - if err := stopLinuxDaemon(d.config.IsRoot); err != nil { - d.logger.Err(err).Msg("Failed to stop daemon service") - return err + if isS6Available() { + if err := stopS6Daemon(d.config.HomeDir, d.config.IsRoot); err != nil { + d.logger.Err(err).Msg("Failed to stop S6 daemon service") + return err + } + } else { + if err := stopLinuxDaemon(d.config.IsRoot); err != nil { + d.logger.Err(err).Msg("Failed to stop daemon service") + return err + } } case config.MacOS: if err := stopMacOSDaemon(d.config.HomeDir, d.config.IsRoot); err != nil { @@ -235,6 +299,9 @@ func (d *Daemon) ReloadDaemon() error { switch d.config.Os { case config.Linux: + if isS6Available() { + return reloadS6Daemon(d.config.HomeDir, d.config.IsRoot) + } return reloadLinuxDaemon(d.config.IsRoot) case config.MacOS: return reloadMacOSDaemon(d.config.HomeDir, d.config.IsRoot) @@ -245,6 +312,80 @@ func (d *Daemon) ReloadDaemon() error { } +// startS6Daemon starts the daemon service on S6 +func startS6Daemon(homeDir string, isRoot bool) error { + servicePath := filepath.Join(homeDir, S6UserServiceDir) + if isRoot { + servicePath = S6RootServiceDir + } + serviceDirPath := filepath.Join(servicePath, S6ServiceName) + + // Remove the down file if it exists to allow the service to start + downFilePath := filepath.Join(serviceDirPath, S6ServiceDownFilename) + if exists, _ := afero.Exists(util.Fs, downFilePath); exists { + if err := util.Fs.Remove(downFilePath); err != nil { + return fmt.Errorf("failed to remove S6 down file: %v", err) + } + } + + // Signal service to start + cmd := exec.Command("s6-svc", "-u", serviceDirPath) + var stderr bytes.Buffer + cmd.Stderr = &stderr + + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to start S6 service: %v", stderr.String()) + } + + return nil +} + +// stopS6Daemon stops the daemon service on S6 +func stopS6Daemon(homeDir string, isRoot bool) error { + servicePath := filepath.Join(homeDir, S6UserServiceDir) + if isRoot { + servicePath = S6RootServiceDir + } + serviceDirPath := filepath.Join(servicePath, S6ServiceName) + + // Create a down file to prevent the service from restarting + downFilePath := filepath.Join(serviceDirPath, S6ServiceDownFilename) + if err := afero.WriteFile(util.Fs, downFilePath, []byte(""), ServicePermission); err != nil { + return fmt.Errorf("failed to create S6 down file: %v", err) + } + + // Signal service to stop + cmd := exec.Command("s6-svc", "-d", serviceDirPath) + var stderr bytes.Buffer + cmd.Stderr = &stderr + + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to stop S6 service: %v", stderr.String()) + } + + return nil +} + +// reloadS6Daemon reloads the daemon service on S6 +func reloadS6Daemon(homeDir string, isRoot bool) error { + servicePath := filepath.Join(homeDir, S6UserServiceDir) + if isRoot { + servicePath = S6RootServiceDir + } + serviceDirPath := filepath.Join(servicePath, S6ServiceName) + + // Signal service to reload (HUP signal) + cmd := exec.Command("s6-svc", "-h", serviceDirPath) + var stderr bytes.Buffer + cmd.Stderr = &stderr + + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to reload S6 service: %v", stderr.String()) + } + + return nil +} + // reloadLinuxDaemon reloads the daemon service on Linux using systemctl. func reloadLinuxDaemon(isRoot bool) error { cmd := exec.Command("systemctl", "--user", "reload", ServicedName) @@ -368,18 +509,29 @@ func buildConfigurationPath(os config.OSType, isRoot bool, homeDir string) (stri switch os { case config.Linux: - servicePath := filepath.Join(homeDir, UserServicedFilePath) + if isS6Available() { + servicePath := filepath.Join(homeDir, S6UserServiceDir) + if isRoot { + servicePath = S6RootServiceDir + } - if !checkLogindService() && !isRoot { - return "", "", fmt.Errorf("logind service is not available") - } + filePath = filepath.Join(servicePath, S6ServiceName, S6ServiceRunFilename) + templateLocation = S6DaemonTemplateLocation + } else { + servicePath := filepath.Join(homeDir, UserServicedFilePath) - if isRoot { - servicePath = RootServicedFilePath + if !checkLogindService() && !isRoot { + return "", "", fmt.Errorf("logind service is not available") + } + + if isRoot { + servicePath = RootServicedFilePath + } + + filePath = filepath.Join(servicePath, ServicedName) + templateLocation = LinuxDaemonTemplateLocation } - filePath = filepath.Join(servicePath, ServicedName) - templateLocation = LinuxDaemonTemplateLocation case config.MacOS: servicePath := filepath.Join(homeDir, PlistFilePath) @@ -412,3 +564,10 @@ func checkLogindService() bool { return true } + +// isS6Available checks if s6 service manager is available on the system +func isS6Available() bool { + cmd := exec.Command("which", "s6-svscan") + err := cmd.Run() + return err == nil +} diff --git a/daemon/services/oda.s6 b/daemon/services/oda.s6 new file mode 100644 index 0000000..7c15669 --- /dev/null +++ b/daemon/services/oda.s6 @@ -0,0 +1,17 @@ +// File: services/oda.s6 +#!/bin/sh +# S6 service run script for oda + +{{if .Username}} +exec setuidgid {{.Username}} {{.Group}} \ +{{end}} +exec {{.BinaryPath}} {{.CollectCommand}} + +// File: services/oda.s6.log +#!/bin/sh +# S6 logging service for oda + +LOGDIR="{{.Home}}/.oda/logs" +mkdir -p "$LOGDIR" + +exec s6-log n20 s16777215 "$LOGDIR" diff --git a/descriptor.bin b/descriptor.bin index 641855a444e5454777b1f1309bf54a4575262360..d96171a8b04766cd3c0113870f9c7fc66837c923 100644 GIT binary patch delta 80 zcmdmFJI8hdHz#BNW**KrEZUY_T Date: Wed, 26 Mar 2025 14:29:45 +0100 Subject: [PATCH 2/9] Add overlay and debug logs --- daemon/daemon.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index cdfc0df..cb5f346 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -87,6 +87,7 @@ func (d *Daemon) InstallDaemonConfiguration() error { // If we run S6, we need to create the service directory and log directory if isS6Available() && d.config.Os == config.Linux { + d.logger.Debug().Msg("S6 service manager detected") serviceDir := filepath.Dir(filePath) logDir := filepath.Join(serviceDir, S6ServiceLogDir) @@ -567,7 +568,18 @@ func checkLogindService() bool { // isS6Available checks if s6 service manager is available on the system func isS6Available() bool { - cmd := exec.Command("which", "s6-svscan") - err := cmd.Run() - return err == nil + // First check for s6-overlay by looking for the /init binary provided by s6-overlay + if _, err := exec.LookPath("/init"); err == nil { + // Check if it's actually s6-overlay + cmd := exec.Command("grep", "-q", "s6-overlay", "/init") + if err := cmd.Run(); err == nil { + return true + } + } + + // Fall back to checking for standard s6 tools + _, err1 := exec.LookPath("s6-svscan") + _, err2 := exec.LookPath("s6-svc") + + return err1 == nil && err2 == nil } From 8e53dd6d7ea20d39840885478a9a1b0d4e657816 Mon Sep 17 00:00:00 2001 From: tzvonimir Date: Wed, 26 Mar 2025 14:43:30 +0100 Subject: [PATCH 3/9] Update s6 config --- daemon/daemon.go | 28 ---------------------------- daemon/services/oda.s6 | 10 ---------- 2 files changed, 38 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index cb5f346..e56b18d 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -89,39 +89,11 @@ func (d *Daemon) InstallDaemonConfiguration() error { if isS6Available() && d.config.Os == config.Linux { d.logger.Debug().Msg("S6 service manager detected") serviceDir := filepath.Dir(filePath) - logDir := filepath.Join(serviceDir, S6ServiceLogDir) if err := util.Fs.MkdirAll(serviceDir, DirPermission); err != nil { d.logger.Err(err).Msg("Failed to create S6 service directory") return fmt.Errorf("failed to create S6 service directory: %w", err) } - - if err := util.Fs.MkdirAll(logDir, DirPermission); err != nil { - d.logger.Err(err).Msg("Failed to create S6 log directory") - return fmt.Errorf("failed to create S6 log directory: %w", err) - } - - logRunPath := filepath.Join(logDir, S6ServiceRunFilename) - logTmpl, err := template.ParseFS(templateFS, S6LogTemplateLocation) - if err != nil { - d.logger.Err(err).Msg("Failed to parse S6 log template") - return err - } - - var logContent bytes.Buffer - logTmpConf := map[string]interface{}{ - "Home": d.config.HomeDir, - } - - if err := logTmpl.Execute(&logContent, logTmpConf); err != nil { - d.logger.Err(err).Msg("Failed to execute S6 log template") - return err - } - - if err := afero.WriteFile(util.Fs, logRunPath, logContent.Bytes(), 0755); err != nil { - d.logger.Err(err).Msg("Failed to write S6 log run file") - return fmt.Errorf("failed to write S6 log run file: %w", err) - } } tmpl, err := template.ParseFS(templateFS, templatePath) diff --git a/daemon/services/oda.s6 b/daemon/services/oda.s6 index 7c15669..428a68d 100644 --- a/daemon/services/oda.s6 +++ b/daemon/services/oda.s6 @@ -1,4 +1,3 @@ -// File: services/oda.s6 #!/bin/sh # S6 service run script for oda @@ -6,12 +5,3 @@ exec setuidgid {{.Username}} {{.Group}} \ {{end}} exec {{.BinaryPath}} {{.CollectCommand}} - -// File: services/oda.s6.log -#!/bin/sh -# S6 logging service for oda - -LOGDIR="{{.Home}}/.oda/logs" -mkdir -p "$LOGDIR" - -exec s6-log n20 s16777215 "$LOGDIR" From 05636018118e1b1c67811d6314fb01b6775cd281 Mon Sep 17 00:00:00 2001 From: tzvonimir Date: Wed, 26 Mar 2025 15:03:06 +0100 Subject: [PATCH 4/9] Modify s6 start --- daemon/daemon.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index e56b18d..67ea9f4 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -293,7 +293,8 @@ func startS6Daemon(homeDir string, isRoot bool) error { } serviceDirPath := filepath.Join(servicePath, S6ServiceName) - // Remove the down file if it exists to allow the service to start + // For s6-overlay, simply removing the down file should be enough + // The service will be automatically started when the directory is scanned downFilePath := filepath.Join(serviceDirPath, S6ServiceDownFilename) if exists, _ := afero.Exists(util.Fs, downFilePath); exists { if err := util.Fs.Remove(downFilePath); err != nil { @@ -301,13 +302,20 @@ func startS6Daemon(homeDir string, isRoot bool) error { } } - // Signal service to start - cmd := exec.Command("s6-svc", "-u", serviceDirPath) + // Try using touch on the run file to trigger a restart + runFilePath := filepath.Join(serviceDirPath, S6ServiceRunFilename) + cmd := exec.Command("touch", runFilePath) var stderr bytes.Buffer cmd.Stderr = &stderr if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to start S6 service: %v", stderr.String()) + // If touch fails, fall back to using s6-svc as a last resort + fallbackCmd := exec.Command("s6-svc", "-u", serviceDirPath) + fallbackCmd.Stderr = &stderr + + if err := fallbackCmd.Run(); err != nil { + return fmt.Errorf("failed to start S6 service: %v", stderr.String()) + } } return nil From 82f900421d5b675febb5e2c0b3c867ad2fca401d Mon Sep 17 00:00:00 2001 From: tzvonimir Date: Wed, 26 Mar 2025 15:15:22 +0100 Subject: [PATCH 5/9] Modify shell collection --- cmd/cmd.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/cmd.go b/cmd/cmd.go index 5a890bd..c07f562 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -363,6 +363,7 @@ func install(cmd *cobra.Command, _ []string) error { } for shellType, shellLocation := range user.Conf.ShellTypeToLocation { + logging.Log.Debug().Msgf("Installing shell configuration for %s", shellLocation) shellConfig := &shell.Config{ ShellType: config.ShellType(shellType), ShellLocation: shellLocation, From 5310089639c152c1ff3faa6ddd2d13983eb2f44e Mon Sep 17 00:00:00 2001 From: tzvonimir Date: Wed, 26 Mar 2025 15:22:37 +0100 Subject: [PATCH 6/9] Don't break when data is not stored --- collector/collector.go | 2 +- daemon/daemon.go | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/collector/collector.go b/collector/collector.go index e8f3ee8..1e54200 100644 --- a/collector/collector.go +++ b/collector/collector.go @@ -360,8 +360,8 @@ func (c *Collector) handleEndCommand(parts []string) error { c.logger.Debug().Msgf("Command: %+v", command) if err := InsertCommand(command); err != nil { + // we should probably not return here, but continue with the rest of the commands c.logger.Error().Err(err).Msg("Failed to insert command") - return err } delete(c.collectionConfig.ongoingCommands, parts[4]) diff --git a/daemon/daemon.go b/daemon/daemon.go index 67ea9f4..eb12ad0 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -329,19 +329,20 @@ func stopS6Daemon(homeDir string, isRoot bool) error { } serviceDirPath := filepath.Join(servicePath, S6ServiceName) - // Create a down file to prevent the service from restarting + // Create a down file to prevent the service from starting downFilePath := filepath.Join(serviceDirPath, S6ServiceDownFilename) if err := afero.WriteFile(util.Fs, downFilePath, []byte(""), ServicePermission); err != nil { return fmt.Errorf("failed to create S6 down file: %v", err) } - // Signal service to stop + // Try using s6-svc to send the stop signal cmd := exec.Command("s6-svc", "-d", serviceDirPath) var stderr bytes.Buffer cmd.Stderr = &stderr if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to stop S6 service: %v", stderr.String()) + // Log the error but don't fail, the down file is the most important part + fmt.Printf("Warning: s6-svc command failed, but down file was created: %v\n", stderr.String()) } return nil From d11574e24db75d35b7dadcfe76913a4339c5d5f2 Mon Sep 17 00:00:00 2001 From: tzvonimir Date: Wed, 26 Mar 2025 15:27:00 +0100 Subject: [PATCH 7/9] Send process and metrics logs --- client/client.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/client.go b/client/client.go index 45157a9..afa98e0 100644 --- a/client/client.go +++ b/client/client.go @@ -57,6 +57,8 @@ func (c *Client) connect() error { // SendCommands sends a list of commands to the server func (c *Client) SendCommands(commands []*gen.Command, auth *gen.Auth) error { + c.logger.Debug().Msg("Sending commands") + req := &gen.SendCommandsRequest{ Commands: commands, Auth: auth, @@ -76,6 +78,8 @@ func (c *Client) SendCommands(commands []*gen.Command, auth *gen.Auth) error { // SendProcesses sends a list of processes to the server func (c *Client) SendProcesses(processes []*gen.Process, auth *gen.Auth) error { + c.logger.Debug().Msg("Sending processes") + req := &gen.SendProcessesRequest{ Processes: processes, Auth: auth, From 83675270476be0ef99fe804bfc16d94e4c5e8300 Mon Sep 17 00:00:00 2001 From: tzvonimir Date: Wed, 26 Mar 2025 15:32:13 +0100 Subject: [PATCH 8/9] Fix reload for s6 --- daemon/daemon.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index eb12ad0..99e5827 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -356,13 +356,20 @@ func reloadS6Daemon(homeDir string, isRoot bool) error { } serviceDirPath := filepath.Join(servicePath, S6ServiceName) - // Signal service to reload (HUP signal) - cmd := exec.Command("s6-svc", "-h", serviceDirPath) + // For s6-overlay, touch the run file to reload the service + runFilePath := filepath.Join(serviceDirPath, S6ServiceRunFilename) + touchCmd := exec.Command("touch", runFilePath) var stderr bytes.Buffer - cmd.Stderr = &stderr + touchCmd.Stderr = &stderr - if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to reload S6 service: %v", stderr.String()) + if err := touchCmd.Run(); err != nil { + // If touch fails, try sending a HUP signal via s6-svc + cmd := exec.Command("s6-svc", "-h", serviceDirPath) + cmd.Stderr = &stderr + + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to reload S6 service: %v", stderr.String()) + } } return nil From fa4a410429709eeffeca5ab1381ed77a01674157 Mon Sep 17 00:00:00 2001 From: tzvonimir Date: Wed, 26 Mar 2025 15:35:44 +0100 Subject: [PATCH 9/9] Remove service files --- daemon/daemon.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index 99e5827..66d5c47 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -37,11 +37,7 @@ const ( S6ServiceName = "oda" S6ServiceRunFilename = "run" S6ServiceDownFilename = "down" - S6ServiceFinishFilename = "finish" - S6ServiceLogDir = "log" - S6ServiceLogRunFilename = "run" S6DaemonTemplateLocation = "services/oda.s6" - S6LogTemplateLocation = "services/oda.s6.log" ) // Embedding scripts directory