Files
ArmA-3-web-server/backend/cmd/server/main.go
MrFastwind 914e0fbe48
Some checks failed
CI / build (push) Failing after 3s
Make deployment-friendly defaults: no env vars needed to run
- Go binary defaults DATA_DIR=./data, SERVERFILE_DIR=./serverfiles
  (relative to cwd) — just copy the binary and run
- MODS_DIR, CFG_DIR, PROFILES_DIR are derived from SERVERFILE_DIR
- Removed DEV_DEPLOY_DIR indirection (was only used for development)
- Makefile expands defaults to ../dev-deploy/... directly for dev
2026-07-09 15:42:54 +02:00

175 lines
4.6 KiB
Go

package main
import (
"context"
"io/fs"
"log"
"mime"
"net/http"
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"
"time"
"arma3-web-server/embed"
"arma3-web-server/internal/api"
"arma3-web-server/internal/services"
"github.com/gin-gonic/gin"
)
func main() {
serverfileDir := mustAbs(getEnv("SERVERFILE_DIR", "./serverfiles"))
dataDir := mustAbs(getEnv("DATA_DIR", "./data"))
modsDir := mustAbs(getEnv("MODS_DIR", filepath.Join(serverfileDir, "mods")))
cfgDir := mustAbs(getEnv("CFG_DIR", filepath.Join(serverfileDir, "cfg")))
profilesDir := mustAbs(getEnv("PROFILES_DIR", filepath.Join(serverfileDir, "profiles")))
listen := getEnv("LISTEN", ":8080")
log.Printf("data dir: %s", dataDir)
log.Printf("serverfile dir: %s", serverfileDir)
log.Printf("mods dir: %s", modsDir)
log.Printf("cfg dir: %s", cfgDir)
log.Printf("profiles dir: %s", profilesDir)
log.Printf("listening on %s", listen)
dirs := []string{
dataDir, filepath.Join(dataDir, "modlists"),
serverfileDir, modsDir, cfgDir, profilesDir,
filepath.Join(serverfileDir, "steamapps", "workshop", "content", "107410"),
}
for _, dir := range dirs {
if err := os.MkdirAll(dir, 0755); err != nil {
log.Fatalf("mkdir %s: %v", dir, err)
}
}
settings := services.NewSettingsManager(dataDir)
streamer := services.NewLogStreamer()
configMgr := services.NewConfigManager(cfgDir)
modlistMgr := services.NewModlistManager(dataDir)
process := services.NewProcessManager(serverfileDir, modsDir, cfgDir, profilesDir, settings, modlistMgr, configMgr, streamer)
steamcmd := services.NewSteamCmdManager(serverfileDir, streamer)
scheduler := services.NewScheduler(settings, modlistMgr, steamcmd)
r := gin.Default()
subFS, subErr := fs.Sub(embed.Frontend, "dist")
if subErr == nil {
if _, err := subFS.Open("index.html"); err == nil {
r.NoRoute(func(c *gin.Context) {
path := strings.TrimPrefix(c.Request.URL.Path, "/")
if path == "" {
path = "index.html"
}
data, err := fs.ReadFile(subFS, path)
if err == nil {
ct := mime.TypeByExtension(filepath.Ext(path))
if ct == "" {
ct = "text/html; charset=utf-8"
}
c.Data(http.StatusOK, ct, data)
return
}
data, _ = fs.ReadFile(subFS, "index.html")
c.Data(http.StatusOK, "text/html; charset=utf-8", data)
})
log.Print("serving embedded frontend")
}
}
handler := api.New(settings, configMgr, modlistMgr, process, steamcmd, scheduler, streamer, dataDir, serverfileDir, modsDir, cfgDir, profilesDir)
handler.SetupRoutes(r)
// Startup auto-tasks
s, err := settings.Load()
if err != nil {
log.Printf("startup: load settings: %v", err)
} else {
if s.AutoUpdateOnStartup && s.SteamUser != "" {
go func() {
log.Print("startup: auto-updating gameserver")
if err := steamcmd.UpdateGame(s.SteamBranch, s.SteamUser); err != nil {
log.Printf("startup: auto-update game failed: %v", err)
}
}()
}
if s.AutoUpdateModsOnStartup && s.ActiveModlist != "" {
go func() {
log.Print("startup: auto-updating mods")
ml, err := modlistMgr.Get(s.ActiveModlist)
if err != nil {
log.Printf("startup: load modlist for auto-update: %v", err)
return
}
var modIDs []string
for _, m := range ml.Mods {
if m.Enabled {
modIDs = append(modIDs, m.ID)
}
}
if len(modIDs) > 0 {
if err := steamcmd.DownloadMods(modIDs); err != nil {
log.Printf("startup: auto-update mods failed: %v", err)
}
}
}()
}
if s.AutoStartOnStartup && s.WasRunning {
go func() {
log.Print("startup: auto-restarting server (was running before)")
if err := process.Start(); err != nil {
log.Printf("startup: auto-start failed: %v", err)
}
}()
}
}
// Start the scheduled updater
scheduler.Start()
log.Printf("starting server on %s", listen)
srv := &http.Server{Addr: listen, Handler: r}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("server: %v", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Print("shutting down server...")
scheduler.Stop()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("server forced shutdown: %v", err)
}
log.Print("server exited")
}
func mustAbs(p string) string {
a, err := filepath.Abs(p)
if err != nil {
log.Fatalf("abs %s: %v", p, err)
}
return a
}
func getEnv(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}