@@ -10,7 +10,6 @@ import (
1010 "path/filepath"
1111 "runtime"
1212 "strings"
13- "syscall"
1413 "text/template"
1514 "time"
1615)
@@ -453,21 +452,31 @@ func uninstallLinux() error {
453452 return nil
454453}
455454
455+ const crontabCmd = "crontab"
456+
456457// installCron installs a cron job (fallback for systems without systemd/launchd).
457458func installCron (agentPath , _ , _ string ) error {
458459 // Check if crontab is available
459- if _ , err := exec .LookPath ("crontab" ); err != nil {
460+ if _ , err := exec .LookPath (crontabCmd ); err != nil {
460461 return errors .New ("neither systemd user services nor cron are available - manual startup required" )
461462 }
462463
463464 // Get current crontab
464- cmd := exec .Command ("crontab" , "-l" ) //nolint:noctx // local command
465- output , _ := cmd .Output () //nolint:errcheck // Ignore error - no crontab is fine
465+ cmd := exec .Command (crontabCmd , "-l" ) //nolint:noctx // local command
466+ output , _ := cmd .Output () //nolint:errcheck // Ignore error - no crontab is fine
466467 currentCron := string (output )
467468
468- // Check if already installed
469- if strings .Contains (currentCron , agentPath ) {
470- return nil // Already installed
469+ // Check if already installed - look for the agent name in the crontab
470+ if strings .Contains (currentCron , agentName ) {
471+ log .Printf ("[INFO] Cron job for %s already installed, updating entries" , agentName )
472+ // Remove old entries to replace with new ones
473+ var filteredLines []string
474+ for _ , line := range strings .Split (currentCron , "\n " ) {
475+ if ! strings .Contains (line , agentName ) {
476+ filteredLines = append (filteredLines , line )
477+ }
478+ }
479+ currentCron = strings .Join (filteredLines , "\n " )
471480 }
472481
473482 // Add new cron jobs: run at reboot and every 15 minutes
@@ -485,59 +494,60 @@ func installCron(agentPath, _, _ string) error {
485494 }
486495
487496 // Install new crontab
488- cmd = exec .Command ("crontab" , "-" ) //nolint:noctx // local command
497+ log .Printf ("[INFO] Installing cron entries for %s" , agentPath )
498+ cmd = exec .Command (crontabCmd , "-" ) //nolint:noctx // local command
489499 cmd .Stdin = strings .NewReader (newCron )
490500 if err := cmd .Run (); err != nil {
491501 return fmt .Errorf ("failed to install crontab: %w" , err )
492502 }
503+ log .Print ("[INFO] Cron entries installed successfully" )
504+
505+ // Verify the crontab was actually installed
506+ cmd = exec .Command (crontabCmd , "-l" ) //nolint:noctx // local command
507+ if output , err := cmd .Output (); err == nil {
508+ if strings .Contains (string (output ), agentPath ) {
509+ log .Print ("[INFO] Cron entries verified in crontab" )
510+ } else {
511+ log .Print ("[WARN] Cron entries not found in crontab after installation" )
512+ }
513+ }
493514
494515 // Try to start the agent immediately in background
495- // Use nohup to ensure the process survives after installer exits
496516 log .Printf ("[INFO] Starting agent in background: %s" , agentPath )
497517
498- // Check if nohup is available
499- nohupPath , nohupErr := exec .LookPath ("nohup" )
500- if nohupErr == nil {
501- // Use nohup with proper detachment
502- cmd = exec .Command (nohupPath , agentPath ) //nolint:noctx // agent spawns its own context
503- cmd .Stdin = nil
504- // Redirect stdout/stderr to /dev/null to fully detach
505- devNull , err := os .Open ("/dev/null" )
506- if err == nil {
507- cmd .Stdout = devNull
508- cmd .Stderr = devNull
509- defer func () { _ = devNull .Close () }() //nolint:errcheck // defer close
510- }
518+ // Check if nohup is available (it should be on all Unix-like systems including FreeBSD)
519+ nohupPath , err := exec .LookPath ("nohup" )
520+ if err != nil {
521+ log .Printf ("[WARN] nohup not found, agent will start via cron in 15 minutes: %v" , err )
522+ return nil
523+ }
511524
512- // Set process group to detach from parent
513- cmd .SysProcAttr = & syscall.SysProcAttr {
514- Setpgid : true ,
515- }
525+ // Get the directory where the agent is installed
526+ agentDir := filepath .Dir (agentPath )
527+ agentBinary := filepath .Base (agentPath )
516528
517- if err := cmd .Start (); err == nil {
518- // Process started successfully
519- log .Printf ("[INFO] Agent started successfully with PID %d using nohup" , cmd .Process .Pid )
520- // Don't wait for it to finish
521- go func () {
522- _ = cmd .Wait () //nolint:errcheck // Reap the child when it exits
523- }()
524- } else {
525- log .Printf ("[WARN] Failed to start agent with nohup: %v" , err )
526- }
529+ // Use nohup with shell to properly background the process
530+ // Change to the agent directory first so PID file and logs are created in the right place
531+ // The & is crucial for detaching from the parent process
532+ shellCmd := fmt .Sprintf ("cd %s && %s ./%s > /dev/null 2>&1 &" , agentDir , nohupPath , agentBinary )
533+ cmd = exec .Command ("sh" , "-c" , shellCmd ) //nolint:noctx // agent spawns its own context
534+
535+ if err := cmd .Run (); err != nil {
536+ log .Printf ("[WARN] Failed to start agent with nohup: %v (will start via cron in 15 minutes)" , err )
537+ return nil
538+ }
539+
540+ log .Print ("[INFO] Agent started successfully in background using nohup" )
541+
542+ // Give it a moment to start
543+ time .Sleep (500 * time .Millisecond )
544+ // Verify the process is running
545+ checkCmd := exec .Command ("pgrep" , "-f" , agentName ) //nolint:noctx // local command
546+ if output , err := checkCmd .Output (); err == nil && len (output ) > 0 {
547+ pids := strings .TrimSpace (string (output ))
548+ log .Printf ("[INFO] Agent process confirmed running with PID(s): %s" , pids )
527549 } else {
528- // Fallback to direct execution without nohup
529- cmd = exec .Command (agentPath ) //nolint:noctx // agent spawns its own context
530- cmd .Stdin = nil
531- cmd .Stdout = nil
532- cmd .Stderr = nil
533-
534- if err := cmd .Start (); err == nil {
535- // Detach from the process
536- _ = cmd .Process .Release () //nolint:errcheck // best effort cleanup
537- log .Printf ("[INFO] Agent started successfully with PID %d (without nohup)" , cmd .Process .Pid )
538- } else {
539- log .Printf ("[WARN] Failed to start agent immediately: %v (will start via cron in 15 minutes)" , err )
540- }
550+ log .Print ("[WARN] Could not confirm agent is running, but it may have started successfully" )
541551 }
542552
543553 return nil
@@ -546,7 +556,7 @@ func installCron(agentPath, _, _ string) error {
546556// uninstallCron removes cron job.
547557func uninstallCron () error {
548558 // Get current crontab
549- cmd := exec .Command ("crontab" , "-l" ) //nolint:noctx // local command
559+ cmd := exec .Command (crontabCmd , "-l" ) //nolint:noctx // local command
550560 output , err := cmd .Output ()
551561 if err != nil {
552562 return nil //nolint:nilerr // No crontab, nothing to remove
@@ -567,9 +577,9 @@ func uninstallCron() error {
567577 // Install updated crontab
568578 if strings .TrimSpace (newCron ) == "" {
569579 // Remove crontab entirely if empty
570- _ = exec .Command ("crontab" , "-r" ).Run () //nolint:errcheck,noctx // Best effort
580+ _ = exec .Command (crontabCmd , "-r" ).Run () //nolint:errcheck,noctx // Best effort
571581 } else {
572- cmd = exec .Command ("crontab" , "-" ) //nolint:noctx // local command
582+ cmd = exec .Command (crontabCmd , "-" ) //nolint:noctx // local command
573583 cmd .Stdin = strings .NewReader (newCron )
574584 if err := cmd .Run (); err != nil {
575585 return fmt .Errorf ("failed to update crontab: %w" , err )
0 commit comments