Les services système sont des programmes légers qui fonctionnent en arrière-plan sans interface utilisateur graphique . Ils démarrent automatiquement lors du démarrage du système et s'exécutent indépendamment. Leur cycle de vie, qui comprend des opérations telles que le démarrage, l'arrêt et le redémarrage, est géré par Service Control Manager sous Windows, systemd sous Linux (dans la plupart des destructions) et launchd sur macOS.
Contrairement aux applications standard, les services sont conçus pour un fonctionnement continu et sont essentiels pour des tâches telles que la surveillance, la journalisation et d'autres processus en arrière-plan. Sous Linux, ces services sont généralement appelés démons, tandis que sur macOS, ils sont appelés agents de lancement ou démons.
La création de services système multiplateformes nécessite un langage qui équilibre efficacité, convivialité et fiabilité. Go excelle à cet égard pour plusieurs raisons :
Concurrence et performances : les goroutines de Go facilitent l'exécution de plusieurs tâches à la fois, améliorant ainsi l'efficacité et la vitesse sur différentes plates-formes. Associé à une bibliothèque standard robuste, cela minimise les dépendances externes et améliore la compatibilité multiplateforme.
Gestion et stabilité de la mémoire : le garbage collection de Go empêche les fuites de mémoire et maintient les systèmes stables. Sa gestion claire des erreurs facilite également le débogage de services complexes.
Simplicité et maintenabilité : la syntaxe claire de Go simplifie l'écriture et la maintenance des services . Sa capacité à produire des fichiers binaires liés statiquement donne lieu à des fichiers exécutables uniques qui incluent toutes les dépendances nécessaires, éliminant ainsi le besoin d'environnements d'exécution séparés.
Compilation croisée et flexibilité : la prise en charge de Go pour la compilation croisée permet de créer des exécutables pour différents systèmes d'exploitation à partir d'une seule base de code. Avec CGO, Go peut interagir avec des API système de bas niveau, telles que Win32 et Objective-C, offrant ainsi aux développeurs la flexibilité nécessaire pour exploiter les fonctionnalités natives.
Cette procédure pas à pas de code suppose que GO est installé sur votre ordinateur et que vous avez une connaissance de base de la syntaxe de GO, sinon je vous recommande fortement de faire un tour .
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
Initialisez le module Go :
go mod init go-service
Définissez les constantes de configuration dans config.go dans le répertoire internal/platform. Ce fichier centralise toutes les valeurs configurables, facilitant ainsi l'ajustement des paramètres.
Fichier : 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 }
Principales fonctionnalités :
Constantes de service qui seront utilisées lors des configurations de service spécifiques à la plate-forme.
GetInstallDir() fournit des chemins d'installation de service et de fichiers journaux appropriés pour chaque système d'exploitation.
copyFile() utilisé lors de l'installation du service pour copier l'exécutable vers notre chemin spécifique fourni par GetInstallDir().
Dans l'interne/service, implémentez la fonctionnalité de base de votre service. L'implémentation du service de base gère la fonctionnalité principale de notre service.
Dans cet exemple, le service ajoute « Hello World » à un fichier du répertoire personnel de l'utilisateur toutes les 5 minutes.
Fichier :interne/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
Le service implémente les méthodes Start et Stop pour la gestion du cycle de vie :
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 }
La méthode run gère la logique de base du service :
Principales fonctionnalités :
Exécution basée sur des intervalles à l'aide du ticker
Prise en charge de l'annulation contextuelle
Gestion des arrêts en douceur
Journalisation des erreurs
Le service ajoute « Hello World » avec un horodatage toutes les 5 minutes
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 }
Le répertoire internal/platform contient des configurations spécifiques à la plate-forme pour installer, désinstaller et gérer le service.
Dans darwin.go, définissez la logique spécifique à macOS pour créer un fichier .plist, qui gère l'installation et la désinstallation du service à l'aide de launchctl.
Fichier :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 }
Sous Linux, nous utilisons systemd pour gérer le service. Définissez un fichier .service et les méthodes associées.
Fichier :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) } } } }
Pour Windows, utilisez la commande sc pour installer et désinstaller le service.
Fichier :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 }
Enfin, configurez main.go dans cmd/service/main.go pour gérer l'installation, la désinstallation et le démarrage du service.
Fichier :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 }
Pour créer votre service pour différents systèmes d'exploitation, utilisez les variables d'environnement GOOS et GOARCH. Par exemple, pour créer pour 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 }
Pour 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 }
Pour 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
Une fois que vous avez créé votre service pour le système d'exploitation respectif, vous pouvez le gérer à l'aide des commandes suivantes.
Remarque : Assurez-vous d'exécuter les commandes avec les privilèges root, car ces actions nécessitent des autorisations élevées sur toutes les plates-formes.
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 }
Bien que vous puissiez utiliser les commandes et les indicateurs Go pour créer et gérer le service, je vous recommande fortement d'utiliser TaskFile. Il automatise ces processus et fournit :
Commandes cohérentes sur toutes les plateformes
Configuration simple basée sur YAML
Gestion intégrée des dépendances
Tout d'abord, vérifiez si Task est installé :
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 }
S'il n'est pas présent, installez en utilisant :
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 }
Créez un Taskfile.yml à la racine de votre projet :
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 }
Gestion des services (nécessite les privilèges root/administrateur) :
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 }
Construisez pour votre plateforme :
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 }
Builds multiplateformes :
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 }
Liste de toutes les tâches disponibles :
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o go-service.exe ./cmd/service
En suivant cette approche structurée, vous pouvez créer un service propre et modulaire dans Go qui fonctionne de manière transparente sur plusieurs plates-formes. Les spécificités de chaque plateforme sont isolées dans leurs fichiers respectifs, et le fichier main.go reste simple et facile à maintenir.
Pour le code complet, veuillez vous référer à mon référentiel de service Go sur GitHub.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!