package api import ( "os" "path/filepath" "runtime" "syscall" "arma3-web-server/internal/services" "github.com/gin-gonic/gin" "golang.org/x/sys/unix" ) type HealthResponse struct { Server ServerHealth `json:"server"` Paths []PathHealth `json:"paths"` Mods ModsHealth `json:"mods"` Disk []DiskHealth `json:"disk"` SteamCMD SteamCMDHealth `json:"steamcmd"` Frontend FrontendHealth `json:"frontend"` } type ServerHealth struct { Running bool `json:"running"` Installed bool `json:"installed"` BinaryPath string `json:"binary_path,omitempty"` } type PathHealth struct { Path string `json:"path"` Exists bool `json:"exists"` Writable bool `json:"writable"` } type ModsHealth struct { WorkshopCount int `json:"workshop_count"` LocalCount int `json:"local_count"` } type DiskHealth struct { Path string `json:"path"` TotalBytes uint64 `json:"total_bytes"` FreeBytes uint64 `json:"free_bytes"` UsedPercent float64 `json:"used_percent"` } type SteamCMDHealth struct { Running bool `json:"running"` } type FrontendHealth struct { Served bool `json:"served"` Path string `json:"path"` } func (h *Handler) Health(c *gin.Context) { workshopMods := services.ListWorkshopMods(h.serverfileDir) localMods := services.ListLocalMods(h.modsDir) paths := h.checkPaths() disk := h.checkDisk() frontendServed, _ := h.checkFrontend() resp := HealthResponse{ Server: ServerHealth{ Running: h.process.IsRunning(), Installed: checkServerBinary(h.serverfileDir), BinaryPath: serverBinaryPath(h.serverfileDir), }, Paths: paths, Mods: ModsHealth{ WorkshopCount: len(workshopMods), LocalCount: len(localMods), }, Disk: disk, SteamCMD: SteamCMDHealth{ Running: h.steamcmd.IsRunning(), }, Frontend: FrontendHealth{ Served: frontendServed, Path: h.frontendDir, }, } c.JSON(200, resp) } func (h *Handler) checkPaths() []PathHealth { dirs := []string{ h.dataDir, filepath.Join(h.dataDir, "modlists"), h.serverfileDir, h.modsDir, h.cfgDir, h.profilesDir, } result := make([]PathHealth, 0, len(dirs)) for _, dir := range dirs { p := PathHealth{Path: dir} fi, err := os.Stat(dir) if err == nil && fi.IsDir() { p.Exists = true p.Writable = isWritable(dir) } result = append(result, p) } return result } func (h *Handler) checkDisk() []DiskHealth { paths := []string{h.dataDir, h.serverfileDir} seen := make(map[string]bool) var result []DiskHealth for _, p := range paths { info, err := diskUsage(p) if err != nil { continue } key := info.Path if seen[key] { continue } seen[key] = true result = append(result, info) } return result } func (h *Handler) checkFrontend() (bool, string) { if h.frontendDir == "" { return false, "" } fi, err := os.Stat(h.frontendDir) return err == nil && fi.IsDir(), h.frontendDir } func checkServerBinary(serverfileDir string) bool { _, found := findServerBinary(serverfileDir) return found } func serverBinaryPath(serverfileDir string) string { path, _ := findServerBinary(serverfileDir) return path } func findServerBinary(serverfileDir string) (string, bool) { candidates := []string{"arma3server_x64"} if runtime.GOOS == "windows" { candidates = append(candidates, "arma3server_x64.exe") } else { candidates = append(candidates, "arma3server_x64.exe") } for _, name := range candidates { p := filepath.Join(serverfileDir, name) fi, err := os.Stat(p) if err == nil && !fi.IsDir() { return p, true } } return "", false } func isWritable(path string) bool { return unix.Access(path, unix.W_OK) == nil } func diskUsage(path string) (DiskHealth, error) { var stat syscall.Statfs_t if err := syscall.Statfs(path, &stat); err != nil { return DiskHealth{}, err } total := stat.Blocks * uint64(stat.Bsize) free := stat.Bfree * uint64(stat.Bsize) var usedPercent float64 if total > 0 { usedPercent = float64(total-free) / float64(total) * 100 } return DiskHealth{ Path: path, TotalBytes: total, FreeBytes: free, UsedPercent: usedPercent, }, nil }