Files
GaCy-GO/assets_ship.go
T
2026-10-28 18:54:43 +07:00

96 lines
2.8 KiB
Go

//go:build ship
package runtimeassets
import (
"embed"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
)
// Keep the ship build self-contained. Probes and generated Lua files are
// deliberately excluded; only the maintained client scripts are embedded.
//
//go:embed resources res.json teleports.json challenge_data.json mission_unlocks.json server.json freesr-data.json persistent hotfix.json versions.json scripts/damage_analyst_live.lua scripts/freecam.lua scripts/hide_ui.lua scripts/navigation_profiles.lua scripts/ping_rainbow.lua
var embedded embed.FS
var seedFiles = []string{"server.json", "freesr-data.json", "persistent", "hotfix.json", "versions.json"}
func ReadFile(name string) ([]byte, error) {
return fs.ReadFile(embedded, filepath.ToSlash(name))
}
func ReadDir(name string) ([]fs.DirEntry, error) {
return fs.ReadDir(embedded, filepath.ToSlash(name))
}
// Bootstrap places mutable defaults beside the executable, so each pair of
// shipped binaries can run without the source checkout or a resource folder.
// Existing operator settings and saves are never replaced.
func Bootstrap() error {
executable, err := os.Executable()
if err != nil {
return fmt.Errorf("locate shipping executable: %w", err)
}
if configPath := os.Getenv("GAC_CONFIG"); configPath != "" && !filepath.IsAbs(configPath) {
absolute, err := filepath.Abs(configPath)
if err != nil {
return fmt.Errorf("resolve GAC_CONFIG before starting: %w", err)
}
if err := os.Setenv("GAC_CONFIG", absolute); err != nil {
return fmt.Errorf("preserve GAC_CONFIG: %w", err)
}
}
workingDirectory := filepath.Dir(executable)
if err := os.Chdir(workingDirectory); err != nil {
return fmt.Errorf("use executable directory %s: %w", workingDirectory, err)
}
return seedMissingFiles(workingDirectory)
}
func seedMissingFiles(directory string) error {
for _, name := range seedFiles {
target := filepath.Join(directory, filepath.FromSlash(name))
if _, err := os.Stat(target); err == nil {
continue
} else if !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("check %s: %w", name, err)
}
data, err := fs.ReadFile(embedded, name)
if err != nil {
return fmt.Errorf("read embedded startup file %s: %w", name, err)
}
if err := writeIfMissing(target, data); err != nil {
return fmt.Errorf("create startup file %s: %w", name, err)
}
}
return nil
}
func writeIfMissing(target string, data []byte) error {
directory := filepath.Dir(target)
temp, err := os.CreateTemp(directory, ".gac-bootstrap-*.tmp")
if err != nil {
return err
}
tempName := temp.Name()
defer os.Remove(tempName)
if _, err := temp.Write(data); err != nil {
_ = temp.Close()
return err
}
if err := temp.Close(); err != nil {
return err
}
if err := os.Link(tempName, target); err != nil {
if _, statErr := os.Stat(target); statErr == nil {
return nil
}
return err
}
return nil
}