//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 }