🔧 AI Nachrichten Major AI platforms go down in unprecedented simultaneous outage(03.09.2026 um 17:34 Uhr)
🔧 AI Nachrichten ChatGPT, Claude, and Grok Down? Users Report Widespread Outages(03.09.2026 um 19:14 Uhr)
🔧 AI Nachrichten OpenAI Launches GPT-6 Astra, Says We May Have Entered the AGI Era(03.09.2026 um 22:08 Uhr)
🔧 AI Nachrichten Claude Comes to CarPlay as Fifth Major AI Chatbot App(05.09.2026 um 05:31 Uhr)
🔧 AI Nachrichten OpenAI’s GPT-6 Astra Is AGI, Says NVIDIA CEO Jensen Huang(07.09.2026 um 06:31 Uhr)
🔧 AI Nachrichten Blame AI companies for Mac mini and Mac Studio shortage(31.08.2026 um 10:32 Uhr)
🔧 AI Nachrichten Major AI platforms go down in unprecedented simultaneous outage(03.09.2026 um 17:34 Uhr)
🔧 AI Nachrichten ChatGPT, Claude, and Grok Down? Users Report Widespread Outages(03.09.2026 um 19:14 Uhr)
🔧 AI Nachrichten OpenAI Launches GPT-6 Astra, Says We May Have Entered the AGI Era(03.09.2026 um 22:08 Uhr)
🔧 AI Nachrichten Claude Comes to CarPlay as Fifth Major AI Chatbot App(05.09.2026 um 05:31 Uhr)
🔧 AI Nachrichten OpenAI’s GPT-6 Astra Is AGI, Says NVIDIA CEO Jensen Huang(07.09.2026 um 06:31 Uhr)
🔧 AI Nachrichten Blame AI companies for Mac mini and Mac Studio shortage(31.08.2026 um 10:32 Uhr)

🔧 Programmierung 🕛 kürzlich 16 Min Lesezeit
0

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

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

 .






Project Overview






CODE
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









Step 1: Define Configurations



Initialize the Go module:




CODE
go mod init go-service






Define configuration constants in config.go within the internal/platform directory. This file centralizes all configurable values, making it easy to adjust settings.



File: internal/platform/config.go




CODE
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
}






Key features:




  • Service Constants which will be used during Platform-Specific Service Configurations.


  • GetInstallDir() provides appropriate service installation and log file paths for each OS.


  • copyFile() used during service installation to copy the executable to our specific path provided by GetInstallDir().







Step 2: Defining Core Service Logic



In the internal/service, implement the core functionality for your service.The core service implementation handles the main functionality of our service.



In this example, the service appends "Hello World" to a file in the user's home directory every 5 minutes.



File: internal/service/service.go






Service Structure






CODE
type Service struct {
logFile string
stop chan struct{}
wg sync.WaitGroup
started bool
mu sync.Mutex
}









Service Creation






CODE
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
}









Service Lifecycle



The service implements Start and Stop methods for lifecycle management:




CODE
// 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
}









Main Service Loop






CODE
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)
}
}
}
}






The run method handles the core service logic:



Key features:




  • Interval-based execution using ticker


  • Context cancellation support


  • Graceful shutdown handling


  • Error logging







Log Writing



The service appends “Hello World” with a timestamp every 5 minutes




CODE
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
}









Step 3: Creating Platform-Specific Service Configurations



The internal/platform directory contains platform-specific configurations to install, uninstall, and manage the service.






macOS (darwin.go)



In darwin.go, define macOS-specific logic for creating a .plist file, which handles service installation and uninstallation using launchctl.



File:internal/platform/darwin.go




CODE
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
}









Linux (linux.go)



On Linux, we use systemd to manage the service. Define a .service file and related methods.



File:internal/platform/linux.go




CODE

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
}









Windows (windows.go)



For Windows, use the sc command to install and uninstall the service.



File:internal/platform/windows.go




CODE
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
}









Step 4: Main File Setup (main.go)



Finally, configure main.go in cmd/service/main.go to handle installation, uninstallation, and starting the service.



File:cmd/service/main.go




CODE
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
}









Building and Managing Your Service



To build your service for different operating systems, use the GOOS and GOARCH environment variables. For example, to build for Windows:




CODE
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o go-service.exe ./cmd/service






For Linux:




CODE
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o go-service ./cmd/service






For macOS:




CODE
GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -o go-service ./cmd/service









Managing Your Service



Once you have built your service for the respective operating system, you can manage it using the following commands.



Note: Ensure to execute the commands with root privileges, as these actions require elevated permissions on all platforms.





  • Install the Service: Use the --install flag to install the service.




CODE
./go-service --install








  • Check the Status: To check if the service is running, use:




CODE
./go-service --status








  • Uninstall the Service: If you need to remove the service, use the --uninstall flag:




CODE
./go-service --uninstall









Building and Managing Your Service using TaskFile (Optional)



Although you can use Go commands and flags to build and manage the service, I highly recommend using TaskFile. It automates these processes and provides:




  • Consistent commands across all platforms


  • Simple YAML-based configuration


  • Built-in dependency management







Setting Up Task



First, check if Task is installed:




CODE
task --version






If not present, install using:



macOS




CODE
brew install go-task/tap/go-task






Linux




CODE
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d






Windows




CODE
# Using Scoop
scoop install task

# Using Chocolatey
choco install go-task









Task Configuration



Create a Taskfile.yml in your project root:




CODE
version: '3'

vars:
BINARY_NAME: go-service
BUILD_DIR: build

tasks:
build:
desc: Build the service binary for current platform
cmds:
- mkdir -p {{.BUILD_DIR}}
- go build -v -ldflags "-s -w" -o {{.BUILD_DIR}}/{{.BINARY_NAME}} ./cmd/service

build:windows:
desc: Build for Windows
cmds:
- mkdir -p {{.BUILD_DIR}}
- GOOS=windows GOARCH=amd64 go build -v -ldflags "-s -w" -o {{.BUILD_DIR}}/{{.BINARY_NAME}}.exe ./cmd/service

build:linux:
desc: Build for Linux
cmds:
- mkdir -p {{.BUILD_DIR}}
- GOOS=linux GOARCH=amd64 go build -v -ldflags "-s -w" -o {{.BUILD_DIR}}/{{.BINARY_NAME}} ./cmd/service

build:darwin-amd64:
desc: Build for macOS (Intel)
cmds:
- mkdir -p {{.BUILD_DIR}}
- GOOS=darwin GOARCH=amd64 go build -v -ldflags "-s -w" -o {{.BUILD_DIR}}/{{.BINARY_NAME}}-amd64 ./cmd/service

build:darwin-arm64:
desc: Build for macOS (Apple Silicon)
cmds:
- mkdir -p {{.BUILD_DIR}}
- GOOS=darwin GOARCH=arm64 go build -v -ldflags "-s -w" -o {{.BUILD_DIR}}/{{.BINARY_NAME}}-arm64 ./cmd/service

build:darwin:
desc: Build for macOS (Universal Binary)
cmds:
- task: build:darwin-amd64
- task: build:darwin-arm64
- mkdir -p {{.BUILD_DIR}}
- lipo -create -output {{.BUILD_DIR}}/{{.BINARY_NAME}} {{.BUILD_DIR}}/{{.BINARY_NAME}}-amd64 {{.BUILD_DIR}}/{{.BINARY_NAME}}-arm64
- rm {{.BUILD_DIR}}/{{.BINARY_NAME}}-amd64 {{.BUILD_DIR}}/{{.BINARY_NAME}}-arm64

build:all:
desc: Build for all platforms
cmds:
- task: build:windows
- task: build:linux
- task: build:darwin

install:
desc: Install the service
deps: [build]
cmds:
- sudo ./{{.BUILD_DIR}}/{{.BINARY_NAME}} -install

uninstall:
desc: Uninstall the service
cmds:
- sudo ./{{.BUILD_DIR}}/{{.BINARY_NAME}} -uninstall

start:
desc: Start the service
cmds:
- sudo ./{{.BUILD_DIR}}/{{.BINARY_NAME}} -start

stop:
desc: Stop the service
cmds:
- sudo ./{{.BUILD_DIR}}/{{.BINARY_NAME}} -stop

status:
desc: Check service status
cmds:
- ./{{.BUILD_DIR}}/{{.BINARY_NAME}} -status

clean:
desc: Clean build artifacts
cmds:
- rm -rf {{.BUILD_DIR}}

default:
desc: Show available tasks
cmds:
- task --list









Using Task Commands



Service management (requires root/administrator privileges):




CODE
sudo task install       # Install service
task status # Check status
sudo task uninstall # Remove service






Build for your platform:




CODE
task build






Cross-platform builds:




CODE
task windows   # Windows build
task linux # Linux build
task darwin # macOS build






List all available tasks:




CODE
task --list









Conclusion



By following this structured approach, you can create a clean and modular service in Go that works seamlessly across multiple platforms. Each platform’s specifics are isolated in their respective files, and the main.go file remains straightforward and easy to maintain.



For the complete code, please refer to my Go service repository on GitHub.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
3 Quellen
GPT-6 Astra Release Today? OpenAI’s Next Major AI Model Is Almost Here
1 Quelle
Apple accuses OpenAI of destroying evidence as trade-secrets fight intensifies
1 Quelle
Major AI platforms go down in unprecedented simultaneous outage
Ähnliche Beiträge
🔍 Verwandte News

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

Thematisch verwandte Begriffe: Building, CrossPlatform, System, Services · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...