add reloading and stopping from tsctl

This commit is contained in:
2025-11-30 11:26:22 +02:00
parent c51dfce9ec
commit ea7358a35f
6 changed files with 205 additions and 68 deletions

70
cmd/reload.go Normal file
View File

@@ -0,0 +1,70 @@
package cmd
import (
"io/ioutil"
"log/slog"
"os"
"strconv"
"strings"
"syscall"
"git.oblat.lv/alex/triggerssmith/internal/vars"
"github.com/spf13/cobra"
)
var optsReloadCmd = struct {
Debug *bool
PID *int
}{}
func readPID(path string) (int, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return 0, err
}
s := strings.TrimSpace(string(data))
pid, err := strconv.Atoi(s)
if err != nil {
return 0, err
}
return pid, nil
}
var reloadCmd = &cobra.Command{
Use: "reload",
Short: "Reload active server by PID using SIGHUP",
Run: func(cmd *cobra.Command, args []string) {
defer func() {
if r := recover(); r != nil {
slog.Error("Application panicked", slog.Any("error", r))
}
}()
// configure logger
if *optsReloadCmd.Debug {
slog.SetDefault(slog.New(slog.NewTextHandler(cmd.OutOrStdout(), &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})))
} else {
slog.SetDefault(slog.New(slog.NewTextHandler(cmd.OutOrStdout(), &slog.HandlerOptions{Level: slog.LevelInfo})))
}
pid, err := readPID(vars.PID_PATH)
if err != nil {
panic(err)
}
*optsReloadCmd.PID = pid
slog.Debug("restarting server", slog.Int("pid", *optsReloadCmd.PID))
proc, err := os.FindProcess(*optsReloadCmd.PID)
if err != nil {
slog.Error("failed to find process", slog.Int("pid", *optsReloadCmd.PID), slog.String("err", err.Error()))
}
proc.Signal(syscall.SIGHUP)
slog.Debug("done")
},
}
func init() {
optsReloadCmd.Debug = reloadCmd.Flags().BoolP("debug", "d", false, "Enable debug logs")
optsReloadCmd.PID = reloadCmd.Flags().IntP("pid", "p", -1, "Define server PID")
rootCmd.AddCommand(reloadCmd)
}

View File

@@ -5,14 +5,18 @@ import (
"log/slog"
"net"
"net/http"
"os"
"os/signal"
"syscall"
"time"
application "git.oblat.lv/alex/triggerssmith/internal/app"
"git.oblat.lv/alex/triggerssmith/internal/config"
"git.oblat.lv/alex/triggerssmith/internal/vars"
"github.com/spf13/cobra"
)
var opts = struct {
var optsServeCmd = struct {
ConfigPath *string
Debug *bool
}{}
@@ -38,29 +42,49 @@ func loggingMiddleware(next http.Handler) http.Handler {
})
}
func writePID(path string) error {
pid := os.Getpid()
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer f.Close()
_, err = fmt.Fprintf(f, "%d\n", pid)
return err
}
var serveCmd = &cobra.Command{
Use: "serve",
Short: "Start the server",
Run: func(cmd *cobra.Command, args []string) {
// defer func() {
// if r := recover(); r != nil {
// slog.Error("Application panicked", slog.Any("error", r))
// }
// }()
defer func() {
if r := recover(); r != nil {
slog.Error("Application panicked", slog.Any("error", r))
os.Exit(-1)
}
}()
// configure logger
if *opts.Debug {
if *optsServeCmd.Debug {
slog.SetDefault(slog.New(slog.NewTextHandler(cmd.OutOrStdout(), &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})))
} else {
slog.SetDefault(slog.New(slog.NewTextHandler(cmd.OutOrStdout(), &slog.HandlerOptions{Level: slog.LevelInfo})))
}
slog.Debug("Starting server")
pid := os.Getpid()
slog.Debug("Starting server", slog.Int("pid", pid))
if err := writePID(vars.PID_PATH); err != nil {
panic(err)
}
slog.Debug("created pid file", slog.String("path", vars.PID_PATH))
defer os.Remove(vars.PID_PATH)
// load config
slog.Debug("Reading configuration", slog.String("path", *opts.ConfigPath))
cfg, err := config.LoadConfig(*opts.ConfigPath)
slog.Debug("Reading configuration", slog.String("path", *optsServeCmd.ConfigPath))
cfg, err := config.LoadConfig(*optsServeCmd.ConfigPath)
if err != nil {
slog.Error("Failed to load configuration", slog.String("path", *opts.ConfigPath), slog.String("error", err.Error()))
slog.Error("Failed to load configuration", slog.String("path", *optsServeCmd.ConfigPath), slog.String("error", err.Error()))
return
}
slog.Debug("Configuration loaded", slog.Any("config", cfg))
@@ -73,39 +97,6 @@ var serveCmd = &cobra.Command{
}
app.LoadConfiguration(cfg)
/*
// setup handlers
mux := http.NewServeMux()
// static files
staticPath := cfg.Server.StaticFilesPath
slog.Debug("Setting up static file server", slog.String("path", staticPath))
fs := http.FileServer(http.Dir(staticPath))
mux.Handle("/static/", http.StripPrefix("/static/", fs))
handler := loggingMiddleware(mux)
*/
// start server
/*addr := fmt.Sprintf("%s:%d", cfg.Server.Addr, cfg.Server.Port)
slog.Debug("Binding listener", slog.String("address", addr))
ln, err := net.Listen("tcp", addr)
if err != nil {
slog.Error("Failed to start listener", slog.String("address", addr), slog.String("error", err.Error()))
return
}
srv := &http.Server{
Addr: addr,
Handler: handler,
}
slog.Info("Server started", slog.String("address", addr))
if err := srv.Serve(ln); err != nil && err != http.ErrServerClosed {
slog.Error("HTTP server stopped with error", slog.String("error", err.Error()))
}*/
server := app.Server()
mux := http.NewServeMux()
@@ -129,26 +120,49 @@ var serveCmd = &cobra.Command{
slog.Info("Server started", slog.String("address", net.JoinHostPort(cfg.Server.Addr, fmt.Sprintf("%d", cfg.Server.Port))))
}
sigch := make(chan os.Signal, 1)
signal.Notify(sigch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
for true {
fmt.Scanln()
if err := config.ReloadConfig(cfg); err != nil {
slog.Error("Failed to reload configuration", slog.String("error", err.Error()))
} else {
slog.Info("Configuration reloaded", slog.Any("config", cfg))
var addr = net.JoinHostPort(cfg.Server.Addr, fmt.Sprintf("%d", cfg.Server.Port))
err = server.Reload(addr)
if err != nil {
slog.Error("Failed to restart server with new configuration", slog.String("error", err.Error()))
sig := <-sigch
slog.Debug("got signal", slog.Any("os.Signal", sig))
switch sig {
case syscall.SIGHUP:
if err := config.ReloadConfig(cfg); err != nil {
slog.Error("Failed to reload configuration", slog.String("error", err.Error()))
} else {
slog.Info("Server restarted with new configuration", slog.String("address", net.JoinHostPort(cfg.Server.Addr, fmt.Sprintf("%d", cfg.Server.Port))))
slog.Info("Configuration reloaded")
var addr = net.JoinHostPort(cfg.Server.Addr, fmt.Sprintf("%d", cfg.Server.Port))
slog.Debug("New configuration", slog.Any("config", cfg))
err = server.Reload(addr)
if err != nil {
slog.Error("Failed to restart server with new configuration", slog.String("error", err.Error()))
}
}
case syscall.SIGINT:
slog.Info("Stopping server by SIGINT")
err := server.Stop()
if err != nil {
slog.Error("Failed to stop server", slog.String("err", err.Error()))
os.Exit(1)
}
os.Remove(vars.PID_PATH)
return
case syscall.SIGTERM:
slog.Info("Stopping server by SIGTERM")
err := server.Stop()
if err != nil {
slog.Error("Failed to stop server", slog.String("err", err.Error()))
os.Exit(1)
}
os.Remove(vars.PID_PATH)
return
}
}
},
}
func init() {
opts.Debug = serveCmd.Flags().BoolP("debug", "d", false, "Enable debug logs")
opts.ConfigPath = serveCmd.Flags().StringP("config", "c", "config.yaml", "Path to configuration file")
optsServeCmd.Debug = serveCmd.Flags().BoolP("debug", "d", false, "Enable debug logs")
optsServeCmd.ConfigPath = serveCmd.Flags().StringP("config", "c", "config.yaml", "Path to configuration file")
rootCmd.AddCommand(serveCmd)
}

51
cmd/stop.go Normal file
View File

@@ -0,0 +1,51 @@
package cmd
import (
"log/slog"
"os"
"syscall"
"git.oblat.lv/alex/triggerssmith/internal/vars"
"github.com/spf13/cobra"
)
var optsStopCmd = struct {
Debug *bool
PID *int
}{}
var stopCmd = &cobra.Command{
Use: "stop",
Short: "Stop active server by PID using SIGTERM",
Run: func(cmd *cobra.Command, args []string) {
defer func() {
if r := recover(); r != nil {
slog.Error("Application panicked", slog.Any("error", r))
}
}()
// configure logger
if *optsReloadCmd.Debug {
slog.SetDefault(slog.New(slog.NewTextHandler(cmd.OutOrStdout(), &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})))
} else {
slog.SetDefault(slog.New(slog.NewTextHandler(cmd.OutOrStdout(), &slog.HandlerOptions{Level: slog.LevelInfo})))
}
pid, err := readPID(vars.PID_PATH)
if err != nil {
panic(err)
}
*optsStopCmd.PID = pid
slog.Debug("restarting server", slog.Int("pid", *optsStopCmd.PID))
proc, err := os.FindProcess(*optsStopCmd.PID)
if err != nil {
slog.Error("failed to find process", slog.Int("pid", *optsStopCmd.PID), slog.String("err", err.Error()))
}
proc.Signal(syscall.SIGTERM)
slog.Debug("done")
},
}
func init() {
optsStopCmd.Debug = stopCmd.Flags().BoolP("debug", "d", false, "Enable debug logs")
optsStopCmd.PID = stopCmd.Flags().IntP("pid", "p", -1, "Define server PID")
rootCmd.AddCommand(stopCmd)
}

View File

@@ -94,17 +94,17 @@ type LiveServer struct {
handler http.Handler
active atomic.Value // *instance
mu sync.Mutex
mu sync.Mutex
statusMu sync.Mutex
status Status
status Status
initDone bool
}
type instance struct {
srv *http.Server
ln net.Listener
ln net.Listener
addr string
}
@@ -198,10 +198,10 @@ func (ls *LiveServer) Start(addr string) error {
return err
}
srv := &http.Server{Handler: ls.handler}
ls.active.Store(&instance{
srv: srv,
ln: ln,
srv: srv,
ln: ln,
addr: addr,
})
@@ -249,7 +249,6 @@ func (ls *LiveServer) Stop() error {
return ls.stop(inst)
}
func (ls *LiveServer) Reload(newAddr string) error {
ls.mu.Lock()
oldInstAny := ls.active.Load()
@@ -258,7 +257,7 @@ func (ls *LiveServer) Reload(newAddr string) error {
oldAddr = oldInstAny.(*instance).addr
}
ls.mu.Unlock()
if oldAddr == newAddr {
return nil
}
@@ -447,7 +446,6 @@ func (ls *LiveServer) Reload(newAddr string) error {
// // }
// ls.setStatus(Status{ID: StatusStarting})
// err := ls.Start()
// if err != nil {

View File

@@ -11,10 +11,10 @@ const (
)
type Status struct {
ID StatusID
Err error
ID StatusID
Err error
}
func (s Status) Error() string {
return s.Err.Error()
}
}

4
internal/vars/const.go Normal file
View File

@@ -0,0 +1,4 @@
package vars
const VAR_PATH = "/var/run/triggerssmith/"
const PID_PATH = VAR_PATH + "serve.pid"