
システム サービスは、グラフィカル ユーザー インターフェースを持たずにバックグラウンドで動作する軽量プログラムです。これらはシステムの起動中に自動的に開始され、独立して実行されます。開始、停止、再起動などの操作を含むライフサイクルは、Windows ではサービス コントロール マネージャー、Linux (ほとんどの Destro) では systemd、macOS では launchd によって管理されます。
標準のアプリケーションとは異なり、サービスは継続的な運用向けに設計されており、監視、ロギング、その他のバックグラウンド プロセスなどのタスクに不可欠です。 Linux では、これらのサービスは一般にデーモンと呼ばれますが、macOS では、Launch Agent または Daemon として知られます。
クロスプラットフォームのシステム サービスを作成するには、効率、使いやすさ、信頼性のバランスをとる言語が必要です。 Go は、いくつかの理由からこの点で優れています:
同時実行性とパフォーマンス: Go のゴルーチンを使用すると、複数のタスクを一度に簡単に実行でき、さまざまなプラットフォームでの効率と速度が向上します。堅牢な標準ライブラリと組み合わせることで、外部依存関係が最小限に抑えられ、クロスプラットフォーム互換性が強化されます。
メモリ管理と安定性: Go のガベージ コレクションはメモリ リークを防ぎ、システムを安定に保ちます。明確なエラー処理により、複雑なサービスのデバッグも容易になります。
シンプルさと保守性: Go の明確な構文により、サービスの作成と保守が簡素化されます。静的にリンクされたバイナリを生成する機能により、必要な依存関係がすべて含まれた単一の実行可能ファイルが生成され、個別のランタイム環境が不要になります。
クロスコンパイルと柔軟性: Go のクロスコンパイルのサポートにより、単一のコードベースからさまざまなオペレーティング システム用の実行可能ファイルをビルドできます。 CGO を使用すると、Go は Win32 や Objective-C などの低レベル システム API と対話できるため、開発者はネイティブ機能を柔軟に利用できます。
このコードのウォークスルーは、GO がマシンにインストールされており、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 モジュールを初期化します:
go mod init go-service
internal/platform ディレクトリ内の config.go で構成定数を定義します。このファイルには、構成可能な値がすべて一元化されているため、設定の調整が簡単になります。
ファイル: external/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
}
主な機能:
プラットフォーム固有のサービス構成中に使用されるサービス定数。
GetInstallDir() は、各 OS の適切なサービス インストールとログ ファイルのパスを提供します。
copyFile() は、サービスのインストール中に、GetInstallDir() によって提供される特定のパスに実行可能ファイルをコピーするために使用されます。
内部/サービスで、サービスのコア機能を実装します。コア サービスの実装は、サービスの主要な機能を処理します。
この例では、サービスは 5 分ごとにユーザーのホーム ディレクトリ内のファイルに「Hello World」を追加します。
ファイル: external/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
サービスは、ライフサイクル管理のための Start メソッドと Stop メソッドを実装します。
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
}
run メソッドは、コア サービス ロジックを処理します。
主な機能:
ティッカーを使用したインターバルベースの実行
コンテキストキャンセルのサポート
正常なシャットダウン処理
エラーログ
サービスは 5 分ごとに「Hello World」にタイムスタンプを追加します
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
}
internal/platform ディレクトリには、サービスをインストール、アンインストール、管理するためのプラットフォーム固有の構成が含まれています。
darwin.go で、launchctl を使用してサービスのインストールとアンインストールを処理する .plist ファイルを作成するための macOS 固有のロジックを定義します。
ファイル: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
}
Linux では、systemd を使用してサービスを管理します。 .service ファイルと関連メソッドを定義します。
ファイル: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)
}
}
}
}
Windows の場合、sc コマンドを使用してサービスをインストールおよびアンインストールします。
ファイル: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
}
最後に、cmd/service/main.go で main.go を構成し、インストール、アンインストール、サービスの開始を処理します。
ファイル: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
}
さまざまなオペレーティング システム用のサービスを構築するには、GOOS および GOARCH 環境変数を使用します。たとえば、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
}
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
}
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
それぞれのオペレーティング システム用にサービスを構築したら、次のコマンドを使用してサービスを管理できます。
注: これらの操作にはすべてのプラットフォームで昇格された権限が必要なため、必ず root 権限でコマンドを実行してください。
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
}
サービスの構築と管理には Go コマンドとフラグを使用できますが、TaskFile を使用することを強くお勧めします。これらのプロセスを自動化し、以下を提供します:
すべてのプラットフォームで一貫したコマンド
簡単な YAML ベースの構成
組み込みの依存関係管理
まず、タスクがインストールされているかどうかを確認します:
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
}
存在しない場合は、以下を使用してインストールします:
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
}
プロジェクトのルートに Taskfile.yml を作成します:
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
}
サービス管理 (root/管理者権限が必要):
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
}
お使いのプラットフォームに合わせてビルドします:
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
}
クロスプラットフォーム ビルド:
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
}
利用可能なタスクをすべてリストします:
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o go-service.exe ./cmd/service
この構造化されたアプローチに従うことで、複数のプラットフォーム間でシームレスに動作するクリーンなモジュール式サービスを Go で作成できます。各プラットフォームの詳細はそれぞれのファイルに分離されており、main.go ファイルは単純で保守が容易です。
完全なコードについては、GitHub 上の私の Go サービス リポジトリを参照してください。
以上がGo でのクロスプラットフォーム システム サービスの構築: ステップバイステップ ガイドの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。