mirror of
https://github.com/Xevion/HATray.git
synced 2025-12-05 23:15:09 -06:00
feat!: add linux cross-platform support with systemd unit file
This commit is contained in:
@@ -34,7 +34,7 @@ func (app *App) Pause() error {
|
||||
app.mu.Lock()
|
||||
defer app.mu.Unlock()
|
||||
|
||||
app.logger.Info("Pausing application",
|
||||
app.logger.Info("pausing application",
|
||||
"action", "pause",
|
||||
"previous_state", app.state,
|
||||
"new_state", StatePaused)
|
||||
@@ -46,7 +46,7 @@ func (app *App) Pause() error {
|
||||
|
||||
app.state = StatePaused
|
||||
|
||||
app.logger.Info("Application paused successfully",
|
||||
app.logger.Info("paused successfully",
|
||||
"action", "pause",
|
||||
"state", app.state)
|
||||
|
||||
@@ -58,7 +58,7 @@ func (app *App) Resume() error {
|
||||
app.mu.Lock()
|
||||
defer app.mu.Unlock()
|
||||
|
||||
app.logger.Info("Resuming application",
|
||||
app.logger.Info("resuming application",
|
||||
"action", "resume",
|
||||
"previous_state", app.state,
|
||||
"new_state", StateRunning)
|
||||
@@ -70,7 +70,7 @@ func (app *App) Resume() error {
|
||||
|
||||
app.state = StateRunning
|
||||
|
||||
app.logger.Info("Application resumed successfully",
|
||||
app.logger.Info("resumed successfully",
|
||||
"action", "resume",
|
||||
"state", app.state)
|
||||
|
||||
@@ -79,14 +79,14 @@ func (app *App) Resume() error {
|
||||
|
||||
// Reload pauses the application, re-reads configuration files, then resumes
|
||||
func (a *App) Reload() error {
|
||||
a.logger.Info("Starting application reload",
|
||||
a.logger.Info("starting application reload",
|
||||
"action", "reload",
|
||||
"current_state", a.state)
|
||||
|
||||
// Pause if not already paused
|
||||
if a.state != StatePaused {
|
||||
if err := a.Pause(); err != nil {
|
||||
a.logger.Error("Failed to pause during reload",
|
||||
a.logger.Error("failed to pause during reload",
|
||||
"action", "reload",
|
||||
"error", err)
|
||||
return err
|
||||
@@ -98,18 +98,18 @@ func (a *App) Reload() error {
|
||||
// - Validate configuration
|
||||
// - Update internal state with new configuration
|
||||
|
||||
a.logger.Info("Configuration reloaded successfully",
|
||||
a.logger.Info("configuration reloaded successfully",
|
||||
"action", "reload")
|
||||
|
||||
// Resume the application
|
||||
if err := a.Resume(); err != nil {
|
||||
a.logger.Error("Failed to resume after reload",
|
||||
a.logger.Error("failed to resume after reload",
|
||||
"action", "reload",
|
||||
"error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
a.logger.Info("Application reload completed successfully",
|
||||
a.logger.Info("application reload completed successfully",
|
||||
"action", "reload",
|
||||
"final_state", a.state)
|
||||
|
||||
@@ -128,7 +128,7 @@ func (app *App) Stop() error {
|
||||
app.mu.Lock()
|
||||
defer app.mu.Unlock()
|
||||
|
||||
app.logger.Info("Stopping application",
|
||||
app.logger.Info("stopping application",
|
||||
"action", "stop",
|
||||
"previous_state", app.state,
|
||||
"new_state", StateStopped)
|
||||
@@ -140,7 +140,7 @@ func (app *App) Stop() error {
|
||||
|
||||
app.state = StateStopped
|
||||
|
||||
app.logger.Info("Application stopped successfully",
|
||||
app.logger.Info("application stopped successfully",
|
||||
"action", "stop",
|
||||
"state", app.state)
|
||||
|
||||
|
||||
90
internal/service/linux.go
Normal file
90
internal/service/linux.go
Normal file
@@ -0,0 +1,90 @@
|
||||
//go:build linux
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"ha-tray/internal/app"
|
||||
|
||||
"github.com/coreos/go-systemd/daemon"
|
||||
)
|
||||
|
||||
const serviceName = "HATray"
|
||||
|
||||
// linuxService implements the Service interface for Linux
|
||||
// It integrates with systemd and controls the app layer
|
||||
// according to systemd signals (start, stop, reload)
|
||||
type linuxService struct {
|
||||
logger *slog.Logger
|
||||
app *app.App
|
||||
}
|
||||
|
||||
// NewService creates a new Linux service instance
|
||||
func NewService(logger *slog.Logger) Service {
|
||||
return &linuxService{
|
||||
logger: logger,
|
||||
app: app.NewApp(logger),
|
||||
}
|
||||
}
|
||||
|
||||
// Run implements the Service interface for Linux
|
||||
func (s *linuxService) Run() error {
|
||||
s.logger.Info("starting service")
|
||||
|
||||
// Notify systemd that we are starting
|
||||
daemon.SdNotify(false, "STATUS=Starting HATray...\n")
|
||||
|
||||
// Setup signal handling for systemd
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGUSR1, syscall.SIGUSR2)
|
||||
done := make(chan struct{})
|
||||
|
||||
// Notify systemd that we are ready
|
||||
daemon.SdNotify(false, "READY=1")
|
||||
daemon.SdNotify(false, "STATUS=HATray running\n")
|
||||
|
||||
go func() {
|
||||
for {
|
||||
sig := <-sigs
|
||||
s.logger.Info("signal received", "signal", sig)
|
||||
switch sig {
|
||||
case syscall.SIGINT, syscall.SIGTERM:
|
||||
daemon.SdNotify(false, "STOPPING=1")
|
||||
s.logger.Info("stopping service")
|
||||
s.app.Stop()
|
||||
close(done)
|
||||
return
|
||||
case syscall.SIGHUP:
|
||||
s.logger.Info("reloading service")
|
||||
daemon.SdNotify(false, "RELOADING=1")
|
||||
s.app.Reload()
|
||||
daemon.SdNotify(false, "READY=1")
|
||||
case syscall.SIGUSR1:
|
||||
s.logger.Info("pausing service")
|
||||
s.app.Pause()
|
||||
case syscall.SIGUSR2:
|
||||
s.logger.Info("resuming service")
|
||||
s.app.Resume()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Main loop: heartbeat to systemd
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
s.logger.Info("service stopped")
|
||||
return nil
|
||||
case <-ticker.C:
|
||||
daemon.SdNotify(false, "WATCHDOG=1")
|
||||
s.logger.Debug("heartbeat")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user