5d5b6c99a7
Fix Wake-on-LAN: set SO_BROADCAST on UDP socket, send 3 magic packets, expose broadcast_addr and wake timeout fields in UI, add Test Wake endpoint, surface send errors in job status, bump default wake timeout to 180s.
125 lines
2.9 KiB
Go
125 lines
2.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/syncserver/internal/api"
|
|
"github.com/syncserver/internal/auth"
|
|
"github.com/syncserver/internal/config"
|
|
"github.com/syncserver/internal/db"
|
|
"github.com/syncserver/internal/scheduler"
|
|
"github.com/syncserver/internal/sshmanager"
|
|
"github.com/syncserver/internal/syncengine"
|
|
)
|
|
|
|
var version = "1.0.9"
|
|
|
|
func main() {
|
|
cfgPath := flag.String("config", "", "Path to config.yaml")
|
|
dataDir := flag.String("data-dir", "", "Data directory")
|
|
addr := flag.String("addr", "", "HTTP listen address")
|
|
showVersion := flag.Bool("version", false, "Print version")
|
|
flag.Parse()
|
|
|
|
if *showVersion {
|
|
fmt.Println(version)
|
|
return
|
|
}
|
|
|
|
cfg, err := config.Load(*cfgPath, *dataDir, *addr)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "config error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
cfg.Version = version
|
|
|
|
if err := cfg.EnsureDirs(); err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to create dirs: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
slogHandler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
|
|
Level: slog.LevelInfo,
|
|
})
|
|
slog.SetDefault(slog.New(slogHandler))
|
|
|
|
slog.Info("starting syncserver",
|
|
"version", version,
|
|
"data_dir", cfg.DataDir,
|
|
"addr", cfg.Addr,
|
|
)
|
|
|
|
database, err := db.Open(cfg.DBPath())
|
|
if err != nil {
|
|
slog.Error("failed to open database", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
defer database.Close()
|
|
|
|
if err := database.RunMigrations(); err != nil {
|
|
slog.Error("failed to run migrations", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if err := auth.SeedAdmin(database.DB, cfg.Auth.AdminUser, cfg.Auth.AdminPass); err != nil {
|
|
slog.Warn("admin seeding skipped or failed", "error", err)
|
|
}
|
|
|
|
privKeyPath, _, pubKey, err := sshmanager.EnsureServerKey(cfg.SSHDir())
|
|
if err != nil {
|
|
slog.Error("failed to ensure server SSH key", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
slog.Info("server SSH key ready", "pub_key", pubKey)
|
|
_ = privKeyPath
|
|
|
|
engine := syncengine.New(database, cfg)
|
|
sched := scheduler.New(database, engine, cfg)
|
|
|
|
srv := &http.Server{
|
|
Addr: config.NormalizeAddr(cfg.Addr),
|
|
ReadTimeout: 30 * time.Second,
|
|
WriteTimeout: 60 * time.Second,
|
|
IdleTimeout: 120 * time.Second,
|
|
}
|
|
|
|
apiServer := api.NewServer(cfg, database.DB, engine)
|
|
srv.Handler = apiServer
|
|
|
|
go sched.Start()
|
|
go engine.Start()
|
|
|
|
sigCh := make(chan os.Signal, 1)
|
|
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
go func() {
|
|
slog.Info("http server listening", "addr", srv.Addr)
|
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
slog.Error("http server error", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
}()
|
|
|
|
<-sigCh
|
|
slog.Info("shutting down...")
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
sched.Stop()
|
|
engine.Stop()
|
|
|
|
if err := srv.Shutdown(ctx); err != nil {
|
|
slog.Error("server shutdown error", "error", err)
|
|
}
|
|
slog.Info("bye")
|
|
}
|