Heim > Artikel > Backend-Entwicklung > Aufbau plattformübergreifender Systemdienste in Go: Eine Schritt-für-Schritt-Anleitung
Systemdienste sind einfache Programme, die im Hintergrund ohne grafische Benutzeroberfläche ausgeführt werden. Sie starten automatisch beim Systemstart und laufen unabhängig voneinander. Ihr Lebenszyklus, der Vorgänge wie Starten, Stoppen und Neustarten umfasst, wird von Service Control Manager unter Windows, systemd unter Linux (in den meisten Destros) und launchd unter macOS verwaltet.
Dienste sind im Gegensatz zu Standardanwendungen auf den kontinuierlichen Betrieb ausgelegt und für Aufgaben wie Überwachung, Protokollierung und andere Hintergrundprozesse unerlässlich. Unter Linux werden diese Dienste im Allgemeinen als Daemons bezeichnet, während sie unter macOS als Launch Agents oder Daemons bekannt sind.
Die Erstellung plattformübergreifender Systemdienste erfordert eine Sprache, die Effizienz, Benutzerfreundlichkeit und Zuverlässigkeit in Einklang bringt. Go zeichnet sich in dieser Hinsicht aus mehreren Gründen aus:
Parallelität und Leistung: Die Goroutinen von Go erleichtern die gleichzeitige Ausführung mehrerer Aufgaben und verbessern so die Effizienz und Geschwindigkeit auf verschiedenen Plattformen. In Verbindung mit einer robusten Standardbibliothek minimiert dies externe Abhängigkeiten und verbessert die plattformübergreifende Kompatibilität.
Speicherverwaltung und -stabilität: Die Garbage Collection von Go verhindert Speicherlecks und hält die Systeme stabil. Seine klare Fehlerbehandlung erleichtert auch das Debuggen komplexer Dienste.
Einfachheit und Wartbarkeit: Die klare Syntax von Go vereinfacht das Schreiben und Verwalten von Diensten. Seine Fähigkeit, statisch verknüpfte Binärdateien zu erstellen, führt zu einzelnen ausführbaren Dateien, die alle erforderlichen Abhängigkeiten enthalten, sodass keine separaten Laufzeitumgebungen erforderlich sind.
Cross-Compilation und Flexibilität: Die Unterstützung von Go für Cross-Compilation ermöglicht die Erstellung ausführbarer Dateien für verschiedene Betriebssysteme aus einer einzigen Codebasis. Mit CGO kann Go mit Low-Level-System-APIs wie Win32 und Objective-C interagieren und bietet Entwicklern so die Flexibilität, native Funktionen zu nutzen.
Bei diesem Code-Rundgang wird davon ausgegangen, dass GO auf Ihrem Computer installiert ist und Sie über Grundkenntnisse der GO-Syntax verfügen. Andernfalls würde ich Ihnen „Take A Tour“ wärmstens empfehlen.
go-service/ ├── Makefile # Build and installation automation ├── cmd/ │ └── service/ │ └── main.go # Main entry point with CLI flags and command handling ├── internal/ │ ├── service/ │ │ └── service.go # Core service implementation │ └── platform/ # Platform-specific implementations │ ├── config.go # Configuration constants │ ├── service.go # Cross-platform service interface │ ├── windows.go # Windows-specific service management │ ├── linux.go # Linux-specific systemd service management │ └── darwin.go # macOS-specific launchd service management └── go.mod # Go module definition
Initialisieren Sie das Go-Modul:
go mod init go-service
Definieren Sie Konfigurationskonstanten in config.go im Verzeichnis internal/platform. Diese Datei zentralisiert alle konfigurierbaren Werte und erleichtert so die Anpassung der Einstellungen.
Datei: internal/platform/config.go
package main const ( ServiceName = "go-service" //Update your service name ServiceDisplay = "Go Service" // Update your display name ServiceDesc = "A service that appends 'Hello World' to a file every 5 minutes." // Update your Service Description LogFileName = "go-service.log" // Update your Log file name ) func GetInstallDir() string { switch runtime.GOOS { case "darwin": return "/usr/local/opt/go-service" case "linux": return "/opt/go-service" case "windows": return filepath.Join(os.Getenv("ProgramData"), ServiceName) default: return "" } } func copyFile(src, dst string) error { source, err := os.Open(src) if err != nil { return fmt.Errorf("failed to open source file: %w", err) } defer source.Close() destination, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) if err != nil { return fmt.Errorf("failed to create destination file: %w", err) } defer destination.Close() _, err = io.Copy(destination, source) return err }
Hauptmerkmale:
Dienstkonstanten, die während plattformspezifischer Dienstkonfigurationen verwendet werden.
GetInstallDir() stellt geeignete Dienstinstallations- und Protokolldateipfade für jedes Betriebssystem bereit.
copyFile() wird während der Dienstinstallation verwendet, um die ausführbare Datei in unseren spezifischen Pfad zu kopieren, der von GetInstallDir() bereitgestellt wird.
Implementieren Sie im internen Bereich/Dienst die Kernfunktionalität für Ihren Dienst. Die Kerndienstimplementierung verwaltet die Hauptfunktionalität unseres Dienstes.
In diesem Beispiel hängt der Dienst alle 5 Minuten „Hello World“ an eine Datei im Home-Verzeichnis des Benutzers an.
Datei: internal/service/service.go
go-service/ ├── Makefile # Build and installation automation ├── cmd/ │ └── service/ │ └── main.go # Main entry point with CLI flags and command handling ├── internal/ │ ├── service/ │ │ └── service.go # Core service implementation │ └── platform/ # Platform-specific implementations │ ├── config.go # Configuration constants │ ├── service.go # Cross-platform service interface │ ├── windows.go # Windows-specific service management │ ├── linux.go # Linux-specific systemd service management │ └── darwin.go # macOS-specific launchd service management └── go.mod # Go module definition
go mod init go-service
Der Dienst implementiert Start- und Stoppmethoden für das Lebenszyklusmanagement:
package main const ( ServiceName = "go-service" //Update your service name ServiceDisplay = "Go Service" // Update your display name ServiceDesc = "A service that appends 'Hello World' to a file every 5 minutes." // Update your Service Description LogFileName = "go-service.log" // Update your Log file name ) func GetInstallDir() string { switch runtime.GOOS { case "darwin": return "/usr/local/opt/go-service" case "linux": return "/opt/go-service" case "windows": return filepath.Join(os.Getenv("ProgramData"), ServiceName) default: return "" } } func copyFile(src, dst string) error { source, err := os.Open(src) if err != nil { return fmt.Errorf("failed to open source file: %w", err) } defer source.Close() destination, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) if err != nil { return fmt.Errorf("failed to create destination file: %w", err) } defer destination.Close() _, err = io.Copy(destination, source) return err }
type Service struct { logFile string stop chan struct{} wg sync.WaitGroup started bool mu sync.Mutex }
Die Ausführungsmethode verwaltet die Kerndienstlogik:
Hauptmerkmale:
Intervallbasierte Ausführung mittels Ticker
Kontextstornierungsunterstützung
Anmutige Handhabung beim Herunterfahren
Fehlerprotokollierung
Der Dienst fügt alle 5 Minuten „Hello World“ mit einem Zeitstempel hinzu
func New() (*Service, error) { installDir := platform.GetInstallDir() if installDir == "" { return nil, fmt.Errorf("unsupported operating system: %s", runtime.GOOS) } logFile := filepath.Join(installDir, "logs", platform.LogFileName) return &Service{ logFile: logFile, stop: make(chan struct{}), }, nil }
Das Verzeichnis internal/platform enthält plattformspezifische Konfigurationen zum Installieren, Deinstallieren und Verwalten des Dienstes.
Definieren Sie in darwin.go eine macOS-spezifische Logik zum Erstellen einer .plist-Datei, die die Installation und Deinstallation von Diensten mithilfe von launchctl übernimmt.
Datei:internal/platform/darwin.go
// Start the service func (s *Service) Start(ctx context.Context) error { s.mu.Lock() if s.started { s.mu.Unlock() return fmt.Errorf("service already started") } s.started = true s.mu.Unlock() if err := os.MkdirAll(filepath.Dir(s.logFile), 0755); err != nil { return fmt.Errorf("failed to create log directory: %w", err) } s.wg.Add(1) go s.run(ctx) return nil } // Stop the service gracefully func (s *Service) Stop() error { s.mu.Lock() if !s.started { s.mu.Unlock() return fmt.Errorf("service not started") } s.mu.Unlock() close(s.stop) s.wg.Wait() s.mu.Lock() s.started = false s.mu.Unlock() return nil }
Unter Linux verwenden wir systemd, um den Dienst zu verwalten. Definieren Sie eine .service-Datei und zugehörige Methoden.
Datei:internal/platform/linux.go
func (s *Service) run(ctx context.Context) { defer s.wg.Done() log.Printf("Service started, logging to: %s\n", s.logFile) ticker := time.NewTicker(5 * time.Minute) defer ticker.Stop() if err := s.writeLog(); err != nil { log.Printf("Error writing initial log: %v\n", err) } for { select { case <-ctx.Done(): log.Println("Service stopping due to context cancellation") return case <-s.stop: log.Println("Service stopping due to stop signal") return case <-ticker.C: if err := s.writeLog(); err != nil { log.Printf("Error writing log: %v\n", err) } } } }
Verwenden Sie unter Windows den sc-Befehl, um den Dienst zu installieren und zu deinstallieren.
Datei:internal/platform/windows.go
func (s *Service) writeLog() error { f, err := os.OpenFile(s.logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return fmt.Errorf("failed to open log file: %w", err) } defer f.Close() _, err = f.WriteString(fmt.Sprintf("[%s] Hello World\n", time.Now().Format(time.RFC3339))) if err != nil { return fmt.Errorf("failed to write to log file: %w", err) } return nil }
Konfigurieren Sie abschließend main.go in cmd/service/main.go, um die Installation, Deinstallation und den Start des Dienstes durchzuführen.
Datei:cmd/service/main.go
package platform import ( "fmt" "os" "os/exec" "path/filepath" ) type darwinService struct{} const plistTemplate = `<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>%s</string> <key>ProgramArguments</key> <array> <string>%s</string> <string>-run</string> </array> <key>RunAtLoad</key> <true/> <key>KeepAlive</key> <true/> <key>WorkingDirectory</key> <string>%s</string> </dict> </plist>` func (s *darwinService) Install(execPath string) error { installDir := GetInstallDir() if err := os.MkdirAll(installDir, 0755); err != nil { return fmt.Errorf("failed to create installation directory: %w", err) } // Copy binary to installation directory installedBinary := filepath.Join(installDir, "bin", filepath.Base(execPath)) if err := os.MkdirAll(filepath.Dir(installedBinary), 0755); err != nil { return fmt.Errorf("failed to create bin directory: %w", err) } if err := copyFile(execPath, installedBinary); err != nil { return fmt.Errorf("failed to copy binary: %w", err) } plistPath := filepath.Join("/Library/LaunchDaemons", ServiceName+".plist") content := fmt.Sprintf(plistTemplate, ServiceName, installedBinary, installDir) if err := os.WriteFile(plistPath, []byte(content), 0644); err != nil { return fmt.Errorf("failed to write plist file: %w", err) } if err := exec.Command("launchctl", "load", plistPath).Run(); err != nil { return fmt.Errorf("failed to load service: %w", err) } return nil } func (s *darwinService) Uninstall() error { plistPath := filepath.Join("/Library/LaunchDaemons", ServiceName+".plist") if err := exec.Command("launchctl", "unload", plistPath).Run(); err != nil { return fmt.Errorf("failed to unload service: %w", err) } if err := os.Remove(plistPath); err != nil { return fmt.Errorf("failed to remove plist file: %w", err) } return nil } func (s *darwinService) Status() (bool, error) { err := exec.Command("launchctl", "list", ServiceName).Run() return err == nil, nil } func (s *darwinService) Start() error { if err := exec.Command("launchctl", "start", ServiceName).Run(); err != nil { return fmt.Errorf("failed to start service: %w", err) } return nil } func (s *darwinService) Stop() error { if err := exec.Command("launchctl", "stop", ServiceName).Run(); err != nil { return fmt.Errorf("failed to stop service: %w", err) } return nil }
Um Ihren Dienst für verschiedene Betriebssysteme zu erstellen, verwenden Sie die Umgebungsvariablen GOOS und GOARCH. Zum Beispiel zum Erstellen für Windows:
package platform import ( "fmt" "os" "os/exec" "path/filepath" ) type linuxService struct{} const systemdServiceTemplate = `[Unit] Description=%s [Service] ExecStart=%s -run Restart=always User=root WorkingDirectory=%s [Install] WantedBy=multi-user.target ` func (s *linuxService) Install(execPath string) error { installDir := GetInstallDir() if err := os.MkdirAll(installDir, 0755); err != nil { return fmt.Errorf("failed to create installation directory: %w", err) } installedBinary := filepath.Join(installDir, "bin", filepath.Base(execPath)) if err := os.MkdirAll(filepath.Dir(installedBinary), 0755); err != nil { return fmt.Errorf("failed to create bin directory: %w", err) } if err := copyFile(execPath, installedBinary); err != nil { return fmt.Errorf("failed to copy binary: %w", err) } servicePath := filepath.Join("/etc/systemd/system", ServiceName+".service") content := fmt.Sprintf(systemdServiceTemplate, ServiceDesc, installedBinary, installDir) if err := os.WriteFile(servicePath, []byte(content), 0644); err != nil { return fmt.Errorf("failed to write service file: %w", err) } commands := [][]string{ {"systemctl", "daemon-reload"}, {"systemctl", "enable", ServiceName}, {"systemctl", "start", ServiceName}, } for _, args := range commands { if err := exec.Command(args[0], args[1:]...).Run(); err != nil { return fmt.Errorf("failed to execute %s: %w", args[0], err) } } return nil } func (s *linuxService) Uninstall() error { _ = exec.Command("systemctl", "stop", ServiceName).Run() _ = exec.Command("systemctl", "disable", ServiceName).Run() servicePath := filepath.Join("/etc/systemd/system", ServiceName+".service") if err := os.Remove(servicePath); err != nil { return fmt.Errorf("failed to remove service file: %w", err) } return nil } func (s *linuxService) Status() (bool, error) { output, err := exec.Command("systemctl", "is-active", ServiceName).Output() if err != nil { return false, nil } return string(output) == "active\n", nil } func (s *linuxService) Start() error { if err := exec.Command("systemctl", "start", ServiceName).Run(); err != nil { return fmt.Errorf("failed to start service: %w", err) } return nil } func (s *linuxService) Stop() error { if err := exec.Command("systemctl", "stop", ServiceName).Run(); err != nil { return fmt.Errorf("failed to stop service: %w", err) } return nil }
Für Linux:
package platform import ( "fmt" "os" "os/exec" "path/filepath" "strings" ) type windowsService struct{} func (s *windowsService) Install(execPath string) error { installDir := GetInstallDir() if err := os.MkdirAll(installDir, 0755); err != nil { return fmt.Errorf("failed to create installation directory: %w", err) } installedBinary := filepath.Join(installDir, "bin", filepath.Base(execPath)) if err := os.MkdirAll(filepath.Dir(installedBinary), 0755); err != nil { return fmt.Errorf("failed to create bin directory: %w", err) } if err := copyFile(execPath, installedBinary); err != nil { return fmt.Errorf("failed to copy binary: %w", err) } cmd := exec.Command("sc", "create", ServiceName, "binPath=", fmt.Sprintf("\"%s\" -run", installedBinary), "DisplayName=", ServiceDisplay, "start=", "auto", "obj=", "LocalSystem") if err := cmd.Run(); err != nil { return fmt.Errorf("failed to create service: %w", err) } descCmd := exec.Command("sc", "description", ServiceName, ServiceDesc) if err := descCmd.Run(); err != nil { return fmt.Errorf("failed to set service description: %w", err) } if err := exec.Command("sc", "start", ServiceName).Run(); err != nil { return fmt.Errorf("failed to start service: %w", err) } return nil } func (s *windowsService) Uninstall() error { _ = exec.Command("sc", "stop", ServiceName).Run() if err := exec.Command("sc", "delete", ServiceName).Run(); err != nil { return fmt.Errorf("failed to delete service: %w", err) } // Clean up installation directory installDir := GetInstallDir() if err := os.RemoveAll(installDir); err != nil { return fmt.Errorf("failed to remove installation directory: %w", err) } return nil } func (s *windowsService) Status() (bool, error) { output, err := exec.Command("sc", "query", ServiceName).Output() if err != nil { return false, nil } return strings.Contains(string(output), "RUNNING"), nil } func (s *windowsService) Start() error { if err := exec.Command("sc", "start", ServiceName).Run(); err != nil { return fmt.Errorf("failed to start service: %w", err) } return nil } func (s *windowsService) Stop() error { if err := exec.Command("sc", "stop", ServiceName).Run(); err != nil { return fmt.Errorf("failed to stop service: %w", err) } return nil }
Für macOS:
go-service/ ├── Makefile # Build and installation automation ├── cmd/ │ └── service/ │ └── main.go # Main entry point with CLI flags and command handling ├── internal/ │ ├── service/ │ │ └── service.go # Core service implementation │ └── platform/ # Platform-specific implementations │ ├── config.go # Configuration constants │ ├── service.go # Cross-platform service interface │ ├── windows.go # Windows-specific service management │ ├── linux.go # Linux-specific systemd service management │ └── darwin.go # macOS-specific launchd service management └── go.mod # Go module definition
Sobald Sie Ihren Dienst für das jeweilige Betriebssystem erstellt haben, können Sie ihn mit den folgenden Befehlen verwalten.
Hinweis: Stellen Sie sicher, dass Sie die Befehle mit Root-Rechten ausführen, da diese Aktionen auf allen Plattformen erhöhte Berechtigungen erfordern.
go mod init go-service
package main const ( ServiceName = "go-service" //Update your service name ServiceDisplay = "Go Service" // Update your display name ServiceDesc = "A service that appends 'Hello World' to a file every 5 minutes." // Update your Service Description LogFileName = "go-service.log" // Update your Log file name ) func GetInstallDir() string { switch runtime.GOOS { case "darwin": return "/usr/local/opt/go-service" case "linux": return "/opt/go-service" case "windows": return filepath.Join(os.Getenv("ProgramData"), ServiceName) default: return "" } } func copyFile(src, dst string) error { source, err := os.Open(src) if err != nil { return fmt.Errorf("failed to open source file: %w", err) } defer source.Close() destination, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) if err != nil { return fmt.Errorf("failed to create destination file: %w", err) } defer destination.Close() _, err = io.Copy(destination, source) return err }
type Service struct { logFile string stop chan struct{} wg sync.WaitGroup started bool mu sync.Mutex }
Obwohl Sie Go-Befehle und Flags zum Erstellen und Verwalten des Dienstes verwenden können, empfehle ich dringend die Verwendung von TaskFile. Es automatisiert diese Prozesse und bietet:
Konsistente Befehle auf allen Plattformen
Einfache YAML-basierte Konfiguration
Integriertes Abhängigkeitsmanagement
Überprüfen Sie zunächst, ob Task installiert ist:
func New() (*Service, error) { installDir := platform.GetInstallDir() if installDir == "" { return nil, fmt.Errorf("unsupported operating system: %s", runtime.GOOS) } logFile := filepath.Join(installDir, "logs", platform.LogFileName) return &Service{ logFile: logFile, stop: make(chan struct{}), }, nil }
Wenn nicht vorhanden, installieren Sie es mit:
macOS
// Start the service func (s *Service) Start(ctx context.Context) error { s.mu.Lock() if s.started { s.mu.Unlock() return fmt.Errorf("service already started") } s.started = true s.mu.Unlock() if err := os.MkdirAll(filepath.Dir(s.logFile), 0755); err != nil { return fmt.Errorf("failed to create log directory: %w", err) } s.wg.Add(1) go s.run(ctx) return nil } // Stop the service gracefully func (s *Service) Stop() error { s.mu.Lock() if !s.started { s.mu.Unlock() return fmt.Errorf("service not started") } s.mu.Unlock() close(s.stop) s.wg.Wait() s.mu.Lock() s.started = false s.mu.Unlock() return nil }
Linux
func (s *Service) run(ctx context.Context) { defer s.wg.Done() log.Printf("Service started, logging to: %s\n", s.logFile) ticker := time.NewTicker(5 * time.Minute) defer ticker.Stop() if err := s.writeLog(); err != nil { log.Printf("Error writing initial log: %v\n", err) } for { select { case <-ctx.Done(): log.Println("Service stopping due to context cancellation") return case <-s.stop: log.Println("Service stopping due to stop signal") return case <-ticker.C: if err := s.writeLog(); err != nil { log.Printf("Error writing log: %v\n", err) } } } }
Windows
func (s *Service) writeLog() error { f, err := os.OpenFile(s.logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return fmt.Errorf("failed to open log file: %w", err) } defer f.Close() _, err = f.WriteString(fmt.Sprintf("[%s] Hello World\n", time.Now().Format(time.RFC3339))) if err != nil { return fmt.Errorf("failed to write to log file: %w", err) } return nil }
Erstellen Sie eine Taskfile.yml in Ihrem Projektstammverzeichnis:
package platform import ( "fmt" "os" "os/exec" "path/filepath" ) type darwinService struct{} const plistTemplate = `<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>%s</string> <key>ProgramArguments</key> <array> <string>%s</string> <string>-run</string> </array> <key>RunAtLoad</key> <true/> <key>KeepAlive</key> <true/> <key>WorkingDirectory</key> <string>%s</string> </dict> </plist>` func (s *darwinService) Install(execPath string) error { installDir := GetInstallDir() if err := os.MkdirAll(installDir, 0755); err != nil { return fmt.Errorf("failed to create installation directory: %w", err) } // Copy binary to installation directory installedBinary := filepath.Join(installDir, "bin", filepath.Base(execPath)) if err := os.MkdirAll(filepath.Dir(installedBinary), 0755); err != nil { return fmt.Errorf("failed to create bin directory: %w", err) } if err := copyFile(execPath, installedBinary); err != nil { return fmt.Errorf("failed to copy binary: %w", err) } plistPath := filepath.Join("/Library/LaunchDaemons", ServiceName+".plist") content := fmt.Sprintf(plistTemplate, ServiceName, installedBinary, installDir) if err := os.WriteFile(plistPath, []byte(content), 0644); err != nil { return fmt.Errorf("failed to write plist file: %w", err) } if err := exec.Command("launchctl", "load", plistPath).Run(); err != nil { return fmt.Errorf("failed to load service: %w", err) } return nil } func (s *darwinService) Uninstall() error { plistPath := filepath.Join("/Library/LaunchDaemons", ServiceName+".plist") if err := exec.Command("launchctl", "unload", plistPath).Run(); err != nil { return fmt.Errorf("failed to unload service: %w", err) } if err := os.Remove(plistPath); err != nil { return fmt.Errorf("failed to remove plist file: %w", err) } return nil } func (s *darwinService) Status() (bool, error) { err := exec.Command("launchctl", "list", ServiceName).Run() return err == nil, nil } func (s *darwinService) Start() error { if err := exec.Command("launchctl", "start", ServiceName).Run(); err != nil { return fmt.Errorf("failed to start service: %w", err) } return nil } func (s *darwinService) Stop() error { if err := exec.Command("launchctl", "stop", ServiceName).Run(); err != nil { return fmt.Errorf("failed to stop service: %w", err) } return nil }
Dienstverwaltung (erfordert Root-/Administratorrechte):
package platform import ( "fmt" "os" "os/exec" "path/filepath" ) type linuxService struct{} const systemdServiceTemplate = `[Unit] Description=%s [Service] ExecStart=%s -run Restart=always User=root WorkingDirectory=%s [Install] WantedBy=multi-user.target ` func (s *linuxService) Install(execPath string) error { installDir := GetInstallDir() if err := os.MkdirAll(installDir, 0755); err != nil { return fmt.Errorf("failed to create installation directory: %w", err) } installedBinary := filepath.Join(installDir, "bin", filepath.Base(execPath)) if err := os.MkdirAll(filepath.Dir(installedBinary), 0755); err != nil { return fmt.Errorf("failed to create bin directory: %w", err) } if err := copyFile(execPath, installedBinary); err != nil { return fmt.Errorf("failed to copy binary: %w", err) } servicePath := filepath.Join("/etc/systemd/system", ServiceName+".service") content := fmt.Sprintf(systemdServiceTemplate, ServiceDesc, installedBinary, installDir) if err := os.WriteFile(servicePath, []byte(content), 0644); err != nil { return fmt.Errorf("failed to write service file: %w", err) } commands := [][]string{ {"systemctl", "daemon-reload"}, {"systemctl", "enable", ServiceName}, {"systemctl", "start", ServiceName}, } for _, args := range commands { if err := exec.Command(args[0], args[1:]...).Run(); err != nil { return fmt.Errorf("failed to execute %s: %w", args[0], err) } } return nil } func (s *linuxService) Uninstall() error { _ = exec.Command("systemctl", "stop", ServiceName).Run() _ = exec.Command("systemctl", "disable", ServiceName).Run() servicePath := filepath.Join("/etc/systemd/system", ServiceName+".service") if err := os.Remove(servicePath); err != nil { return fmt.Errorf("failed to remove service file: %w", err) } return nil } func (s *linuxService) Status() (bool, error) { output, err := exec.Command("systemctl", "is-active", ServiceName).Output() if err != nil { return false, nil } return string(output) == "active\n", nil } func (s *linuxService) Start() error { if err := exec.Command("systemctl", "start", ServiceName).Run(); err != nil { return fmt.Errorf("failed to start service: %w", err) } return nil } func (s *linuxService) Stop() error { if err := exec.Command("systemctl", "stop", ServiceName).Run(); err != nil { return fmt.Errorf("failed to stop service: %w", err) } return nil }
Für Ihre Plattform erstellen:
package platform import ( "fmt" "os" "os/exec" "path/filepath" "strings" ) type windowsService struct{} func (s *windowsService) Install(execPath string) error { installDir := GetInstallDir() if err := os.MkdirAll(installDir, 0755); err != nil { return fmt.Errorf("failed to create installation directory: %w", err) } installedBinary := filepath.Join(installDir, "bin", filepath.Base(execPath)) if err := os.MkdirAll(filepath.Dir(installedBinary), 0755); err != nil { return fmt.Errorf("failed to create bin directory: %w", err) } if err := copyFile(execPath, installedBinary); err != nil { return fmt.Errorf("failed to copy binary: %w", err) } cmd := exec.Command("sc", "create", ServiceName, "binPath=", fmt.Sprintf("\"%s\" -run", installedBinary), "DisplayName=", ServiceDisplay, "start=", "auto", "obj=", "LocalSystem") if err := cmd.Run(); err != nil { return fmt.Errorf("failed to create service: %w", err) } descCmd := exec.Command("sc", "description", ServiceName, ServiceDesc) if err := descCmd.Run(); err != nil { return fmt.Errorf("failed to set service description: %w", err) } if err := exec.Command("sc", "start", ServiceName).Run(); err != nil { return fmt.Errorf("failed to start service: %w", err) } return nil } func (s *windowsService) Uninstall() error { _ = exec.Command("sc", "stop", ServiceName).Run() if err := exec.Command("sc", "delete", ServiceName).Run(); err != nil { return fmt.Errorf("failed to delete service: %w", err) } // Clean up installation directory installDir := GetInstallDir() if err := os.RemoveAll(installDir); err != nil { return fmt.Errorf("failed to remove installation directory: %w", err) } return nil } func (s *windowsService) Status() (bool, error) { output, err := exec.Command("sc", "query", ServiceName).Output() if err != nil { return false, nil } return strings.Contains(string(output), "RUNNING"), nil } func (s *windowsService) Start() error { if err := exec.Command("sc", "start", ServiceName).Run(); err != nil { return fmt.Errorf("failed to start service: %w", err) } return nil } func (s *windowsService) Stop() error { if err := exec.Command("sc", "stop", ServiceName).Run(); err != nil { return fmt.Errorf("failed to stop service: %w", err) } return nil }
Plattformübergreifende Builds:
package main import ( "context" "flag" "fmt" "log" "os" "os/signal" "syscall" "time" "go-service/internal/platform" "go-service/internal/service" ) func main() { log.SetFlags(log.LstdFlags | log.Lmicroseconds) install := flag.Bool("install", false, "Install the service") uninstall := flag.Bool("uninstall", false, "Uninstall the service") status := flag.Bool("status", false, "Check service status") start := flag.Bool("start", false, "Start the service") stop := flag.Bool("stop", false, "Stop the service") runWorker := flag.Bool("run", false, "Run the service worker") flag.Parse() if err := handleCommand(*install, *uninstall, *status, *start, *stop, *runWorker); err != nil { log.Fatal(err) } } func handleCommand(install, uninstall, status, start, stop, runWorker bool) error { platformSvc, err := platform.NewService() if err != nil { return err } execPath, err := os.Executable() if err != nil { return fmt.Errorf("failed to get executable path: %w", err) } switch { case install: return platformSvc.Install(execPath) case uninstall: return platformSvc.Uninstall() case status: running, err := platformSvc.Status() if err != nil { return err } fmt.Printf("Service is %s\n", map[bool]string{true: "running", false: "stopped"}[running]) return nil case start: return platformSvc.Start() case stop: return platformSvc.Stop() case runWorker: return runService() default: return fmt.Errorf("no command specified") } } func runService() error { svc, err := service.New() if err != nil { return fmt.Errorf("failed to create service: %w", err) } ctx, cancel := context.WithCancel(context.Background()) defer cancel() sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) log.Println("Starting service...") if err := svc.Start(ctx); err != nil { return fmt.Errorf("failed to start service: %w", err) } log.Println("Service started, waiting for shutdown signal...") <-sigChan log.Println("Shutdown signal received, stopping service...") if err := svc.Stop(); err != nil { return fmt.Errorf("failed to stop service: %w", err) } log.Println("Service stopped successfully") return nil }
Alle verfügbaren Aufgaben auflisten:
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o go-service.exe ./cmd/service
Wenn Sie diesem strukturierten Ansatz folgen, können Sie in Go einen sauberen und modularen Dienst erstellen, der nahtlos auf mehreren Plattformen funktioniert. Die Besonderheiten jeder Plattform sind in ihren jeweiligen Dateien isoliert und die Datei main.go bleibt unkompliziert und leicht zu warten.
Den vollständigen Code finden Sie in meinem Go-Service-Repository auf GitHub.
Das obige ist der detaillierte Inhalt vonAufbau plattformübergreifender Systemdienste in Go: Eine Schritt-für-Schritt-Anleitung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!