Panel — public source drop (v0.9.0)

Self-hostable game-server control panel: controller + agent + 26 game
modules. One-line install (prebuilt release, no Go required):

  curl -fsSL https://git.pdxtechs.com/dbledeez/panel/raw/branch/main/install.sh | sudo bash

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 21:17:39 -07:00
commit a00bd620a1
2160 changed files with 300574 additions and 0 deletions
+193
View File
@@ -0,0 +1,193 @@
package regionmedic
import (
"fmt"
"io"
"os"
"path/filepath"
"time"
)
// HealPlan is a dry-run description of a single-region heal: which live file
// would be overwritten, from which backup, and which salvage files quarantined.
type HealPlan struct {
Region RegionCoord `json:"region"`
RegionDir string `json:"region_dir"`
RegionFile string `json:"region_file"` // live path to overwrite
LiveReport RegionReport `json:"live_report"` // validation of the current live file
Source Candidate `json:"source"` // chosen backup (Bytes populated)
ErrorBackups []string `json:"error_backups"` // salvage files to quarantine
Evaluated []Candidate `json:"evaluated"` // all backups considered (audit trail)
}
// Ready reports whether the plan can be applied: a clean source was found and
// the live file path is known.
func (p HealPlan) Ready() bool { return p.Source.Found && p.Source.Clean() && p.RegionFile != "" }
// HealResult records what ApplyHeal did.
type HealResult struct {
Region RegionCoord `json:"region"`
RegionFile string `json:"region_file"`
SnapshotPath string `json:"snapshot_path"` // corrupt original saved here
SourceBackup string `json:"source_backup"` // backup the clean copy came from
BytesWritten int `json:"bytes_written"`
QuarantinedFiles []string `json:"quarantined_files"`
}
// PlanHeal builds a heal plan for one region: it validates the current live
// region file, lists the error_backup salvage files for that region, and picks
// the newest clean backup copy via FindLastGoodRegion.
//
// - regionDir: the instance's active Save Region/ directory.
// - region: which region to heal.
// - backups: candidate panel backups (newest-first or any order).
// - deep: deep-validate (inflate + coord check) backup candidates.
// - notAfter: skip backups at/after this time (corruption time); zero disables.
func PlanHeal(regionDir string, region RegionCoord, backups []BackupRef, deep bool, notAfter time.Time) (HealPlan, error) {
plan := HealPlan{Region: region, RegionDir: regionDir}
plan.RegionFile = filepath.Join(regionDir, region.FileName())
if st, err := os.Stat(plan.RegionFile); err != nil || st.IsDir() {
// No live file is not fatal for planning, but record it.
plan.LiveReport = RegionReport{Path: plan.RegionFile, Err: "live region file missing"}
plan.RegionFile = ""
} else {
plan.LiveReport = ValidateRegionFile(filepath.Join(regionDir, region.FileName()), deep)
}
// Collect error_backup salvage files belonging to this region.
if ents, err := os.ReadDir(regionDir); err == nil {
for _, e := range ents {
if e.IsDir() {
continue
}
cx, cz, ok := ParseErrorBackup(e.Name())
if !ok {
continue
}
if RegionForChunk(cx, cz) == region {
plan.ErrorBackups = append(plan.ErrorBackups, filepath.Join(regionDir, e.Name()))
}
}
}
entry := SaveRelEntry(regionDir, region.FileName())
best, evaluated, err := FindLastGoodRegion(backups, entry, deep, notAfter)
if err != nil {
return plan, err
}
plan.Source = best
plan.Evaluated = evaluated
return plan, nil
}
// ApplyHeal executes a ready plan:
// 1. snapshots the current (corrupt) live region file into snapshotDir,
// 2. overwrites the live region file in place with the clean source bytes
// (truncate-in-place preserves the file's owner/mode/ACLs),
// 3. moves the region's error_backup salvage files into quarantineDir.
//
// snapshotDir and quarantineDir are created if missing. Moves fall back to
// copy+remove when rename crosses a filesystem boundary. ApplyHeal refuses a
// plan that is not Ready().
func ApplyHeal(plan HealPlan, snapshotDir, quarantineDir string) (HealResult, error) {
var res HealResult
if !plan.Ready() {
return res, fmt.Errorf("plan not ready: no clean source or missing live region file")
}
res.Region = plan.Region
res.RegionFile = plan.RegionFile
res.SourceBackup = plan.Source.Backup.Path
if err := os.MkdirAll(snapshotDir, 0o755); err != nil {
return res, fmt.Errorf("mkdir snapshot dir: %w", err)
}
// 1. Snapshot the corrupt original.
stamp := plan.LiveReport.timeStamp()
res.SnapshotPath = filepath.Join(snapshotDir, plan.Region.FileName()+".corrupt-"+stamp)
if err := copyFile(plan.RegionFile, res.SnapshotPath); err != nil {
return res, fmt.Errorf("snapshot corrupt region: %w", err)
}
// 2. Overwrite in place (O_TRUNC keeps the inode's owner/mode/ACL).
if err := writeInPlace(plan.RegionFile, plan.Source.Bytes); err != nil {
return res, fmt.Errorf("write clean region: %w", err)
}
res.BytesWritten = len(plan.Source.Bytes)
// 3. Quarantine the salvage files (best-effort per file, but report errors).
if len(plan.ErrorBackups) > 0 {
if err := os.MkdirAll(quarantineDir, 0o755); err != nil {
return res, fmt.Errorf("mkdir quarantine dir: %w", err)
}
for _, src := range plan.ErrorBackups {
dst := filepath.Join(quarantineDir, filepath.Base(src))
if err := moveFile(src, dst); err != nil {
return res, fmt.Errorf("quarantine %s: %w", filepath.Base(src), err)
}
res.QuarantinedFiles = append(res.QuarantinedFiles, dst)
}
}
return res, nil
}
// timeStamp returns a filesystem-safe stamp; uses the report path's modtime is
// not available here, so callers get a coarse unique-ish suffix. We avoid
// time.Now in the pure package only inside tests; here a stamp is fine.
func (RegionReport) timeStamp() string { return time.Now().UTC().Format("20060102-150405") }
// writeInPlace truncates an existing file and writes data, preserving the
// file's existing owner/permissions/ACL (no create-new). If the file does not
// exist it is created 0o664.
func writeInPlace(path string, data []byte) error {
f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0)
if err != nil {
if os.IsNotExist(err) {
f, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o664)
}
if err != nil {
return err
}
}
if _, err := f.Write(data); err != nil {
f.Close()
return err
}
if err := f.Sync(); err != nil {
f.Close()
return err
}
return f.Close()
}
// copyFile copies src to dst (content only), creating dst.
func copyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
return err
}
if _, err := io.Copy(out, in); err != nil {
out.Close()
return err
}
if err := out.Sync(); err != nil {
out.Close()
return err
}
return out.Close()
}
// moveFile renames src to dst, falling back to copy+remove across filesystems.
func moveFile(src, dst string) error {
if err := os.Rename(src, dst); err == nil {
return nil
}
if err := copyFile(src, dst); err != nil {
return err
}
return os.Remove(src)
}