検索
ホームページバックエンド開発GolangGo でのクロスプラットフォーム システム サービスの構築: ステップバイステップ ガイド

Building Cross-Platform System Services in Go: A Step-by-Step Guide

システムサービスとは何ですか?

システム サービスは、グラフィカル ユーザー インターフェースを持たずにバックグラウンドで動作する軽量プログラムです。これらはシステムの起動中に自動的に開始され、独立して実行されます。開始、停止、再起動などの操作を含むライフサイクルは、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 の構文に関する基本的な知識があることを前提としています。そうでない場合は、ツアーに参加することを強くお勧めします。

プロジェクト概要

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

ステップ 1: 構成を定義する

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() によって提供される特定のパスに実行可能ファイルをコピーするために使用されます。

ステップ 2: コアサービスロジックの定義

内部/サービスで、サービスのコア機能を実装します。コア サービスの実装は、サービスの主要な機能を処理します。

この例では、サービスは 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
}

ステップ 3: プラットフォーム固有のサービス構成の作成

internal/platform ディレクトリには、サービスをインストール、アンインストール、管理するためのプラットフォーム固有の構成が含まれています。

macOS (ダーウィン.go)

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 (linux.go)

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 



<h4>
  
  
  Windows (windows.go)
</h4>

<p>Windows の場合、sc コマンドを使用してサービスをインストールおよびアンインストールします。</p>

<p><strong>ファイル:</strong>internal/platform/windows.go<br>
</p>

<pre class="brush:php;toolbar:false">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
}

ステップ 4: メイン ファイルのセットアップ (main.go)

最後に、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"?>

<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></true>
    <key>KeepAlive</key>
    <true></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 権限でコマンドを実行してください。

  • サービスのインストール: --install フラグを使用してサービスをインストールします。
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
}
  • サービスをアンインストールします: サービスを削除する必要がある場合は、--uninstall フラグを使用します。
type Service struct {
 logFile string
 stop    chan struct{}
 wg      sync.WaitGroup
 started bool
 mu      sync.Mutex
}

TaskFile を使用したサービスの構築と管理 (オプション)

サービスの構築と管理には 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 



<p>Windows<br>
</p>

<pre class="brush:php;toolbar:false">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"?>

<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></true>
    <key>KeepAlive</key>
    <true></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...")
 



<p>利用可能なタスクをすべてリストします:<br>
</p>

<pre class="brush:php;toolbar:false">GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o go-service.exe ./cmd/service

結論

この構造化されたアプローチに従うことで、複数のプラットフォーム間でシームレスに動作するクリーンなモジュール式サービスを Go で作成できます。各プラットフォームの詳細はそれぞれのファイルに分離されており、main.go ファイルは単純で保守が容易です。

完全なコードについては、GitHub 上の私の Go サービス リポジトリを参照してください。

以上がGo でのクロスプラットフォーム システム サービスの構築: ステップバイステップ ガイドの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
GOのインターフェイスと多型:コードの再利用性の達成GOのインターフェイスと多型:コードの再利用性の達成Apr 29, 2025 am 12:31 AM

インターフェースアンドポリマスを導入することは、codeReusablivedainability.1)defineinterfacesattherightabstractionlevel.2)useinterfacesfordependencyinjection.3)profilecodetAnageperformanceImpacts。

GOの「init」関数の役割は何ですか?GOの「init」関数の役割は何ですか?Apr 29, 2025 am 12:28 AM

initistingorunsoutomativiviseativeatializepackages andsetuptheenvironment.it'susefulforstingupglobalvariables、resources、およびperformingone-tastasksacrossanypackage.hoer'showitworks:1)Itcanbeusedinpackage、not not-justhe、

GOのインターフェイス構成:複雑な抽象化を構築しますGOのインターフェイス構成:複雑な抽象化を構築しますApr 29, 2025 am 12:24 AM

インターフェイスの組み合わせは、関数を小さな焦点を絞ったインターフェイスに分解することにより、GOプログラミングで複雑な抽象化を構築します。 1)リーダー、ライター、およびより近いインターフェイスを定義します。 2)これらのインターフェイスを組み合わせて、ファイルやネットワークストリームなどの複雑なタイプを作成します。 3)ProcessData関数を使用して、これらの組み合わせインターフェイスを処理する方法を示します。このアプローチはコードの柔軟性、テスト可能性、再利用性を高めますが、過度の断片化と組み合わせの複雑さを避けるために注意する必要があります。

goでinit機能を使用する場合の潜在的な落とし穴と考慮事項goでinit機能を使用する場合の潜在的な落とし穴と考慮事項Apr 29, 2025 am 12:02 AM

intionsingoareautomativitiveedemain foreThemain foreThemaindareusefurfurforseTup butChallenges.1)実行命令:rundistionsrunindediontionOrder.2)テスト:テスト:in functionsMayInterwithests、b

GOの地図をどのように反復しますか?GOの地図をどのように反復しますか?Apr 28, 2025 pm 05:15 PM

記事では、GOのマップを介して反復し、安全なプラクティスに焦点を当て、エントリを変更し、大規模なマップのパフォーマンスに関する考慮事項に焦点を当てています。

GOでどのようにマップを作成しますか?GOでどのようにマップを作成しますか?Apr 28, 2025 pm 05:14 PM

この記事では、初期化方法や要素の追加/更新など、GOのマップの作成と操作について説明します。

ArrayとGoのスライスの違いは何ですか?ArrayとGoのスライスの違いは何ですか?Apr 28, 2025 pm 05:13 PM

この記事では、GOの配列とスライスの違いについて説明し、サイズ、メモリの割り当て、機能の合格、および使用シナリオに焦点を当てています。アレイは固定サイズで、スタックに挿入されていますが、スライスは動的で、しばしばヒープアロークされ、より柔軟です。

GOでどのようにスライスを作成しますか?GOでどのようにスライスを作成しますか?Apr 28, 2025 pm 05:12 PM

この記事では、リテラル、メイク機能、既存のアレイまたはスライスのスライスなど、GOのスライスの作成と初期化について説明します。また、スライスの構文とスライスの長さと容量の決定もカバーします。

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

Eclipse を SAP NetWeaver アプリケーション サーバーと統合します。

AtomエディタMac版ダウンロード

AtomエディタMac版ダウンロード

最も人気のあるオープンソースエディター

SecLists

SecLists

SecLists は、セキュリティ テスターの究極の相棒です。これは、セキュリティ評価中に頻繁に使用されるさまざまな種類のリストを 1 か所にまとめたものです。 SecLists は、セキュリティ テスターが必要とする可能性のあるすべてのリストを便利に提供することで、セキュリティ テストをより効率的かつ生産的にするのに役立ちます。リストの種類には、ユーザー名、パスワード、URL、ファジング ペイロード、機密データ パターン、Web シェルなどが含まれます。テスターはこのリポジトリを新しいテスト マシンにプルするだけで、必要なあらゆる種類のリストにアクセスできるようになります。

SublimeText3 Linux 新バージョン

SublimeText3 Linux 新バージョン

SublimeText3 Linux 最新バージョン