Renaming to vssm and adding a readme
This commit is contained in:
49
README.md
Normal file
49
README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# VSSM (VintageStory Server Manager)
|
||||
*A server manager for VintageStory*
|
||||
|
||||
## IMPORTANT INFORMATION
|
||||
This project is currently in development, and thus everything is subject to change
|
||||
|
||||
## About
|
||||
VSSM allows you to create and manage multiple VS servers
|
||||
|
||||
## Installation and Usage
|
||||
There are no binaries just yet, so you have to download the source files.
|
||||
|
||||
Setup is easy from source, all you need is:
|
||||
- Linux
|
||||
- Go
|
||||
- .NET 10 Runtime
|
||||
|
||||
**If you are using NixOS, a flake.nix is provided, just run `nix develop` for a full development enviroment**
|
||||
|
||||
- Once you have the source code downloaded or cloned, you can run `go build` to generate an executable.
|
||||
- Run `./vssm daemon` to start the daemon - *(For now, theres no background service created by default, but you can run it in the background with `vssm daemon &` and kill the process with `pkill vssm`)*
|
||||
- Run `./vssm` with no arguments to show usage
|
||||
|
||||
## Configuration
|
||||
**For now, the configuration file is stored in `~/.config/vssm/config.json`, but this will be configurable later.**
|
||||
|
||||
|
||||
|
||||
## Roadmap
|
||||
|
||||
- [-] Configuration
|
||||
- [x] Application data path
|
||||
- [x] Declarative Servers
|
||||
|
||||
- [-] Server management
|
||||
- [x] Create servers
|
||||
- [x] Start/Stop servers
|
||||
- [x] Multiple servers support
|
||||
- [x] List servers and their status
|
||||
- [ ] Delete servers
|
||||
- [ ] Automated server backups
|
||||
|
||||
- [ ] Binary releases
|
||||
|
||||
- [-] Other
|
||||
- [-] First class NixOS support
|
||||
- [x] Patch downloaded server binaries for NixOS
|
||||
- [ ] Official nix package or flake
|
||||
- [ ] All configuration options
|
||||
@@ -25,7 +25,7 @@ type AppConfig struct {
|
||||
|
||||
func DefaultConfig() *AppConfig {
|
||||
home, _ := os.UserHomeDir()
|
||||
basePath := filepath.Join(home, ".local", "share", "vs-manager")
|
||||
basePath := filepath.Join(home, ".local", "share", "vssm")
|
||||
|
||||
cfg := &AppConfig{}
|
||||
cfg.Storage.InstallDir = filepath.Join(basePath, "installs")
|
||||
|
||||
26
daemon.go
26
daemon.go
@@ -41,9 +41,22 @@ func StartDaemon(cfg *AppConfig) error {
|
||||
mux.HandleFunc("/instances/command", ds.handleCommand)
|
||||
mux.HandleFunc("/instances/list", ds.handleList)
|
||||
|
||||
corsWrappedHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
||||
|
||||
if r.Method == http.MethodOptions {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
mux.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
server := &http.Server{
|
||||
Addr: cfg.Daemon.ListenAddress,
|
||||
Handler: mux,
|
||||
Handler: corsWrappedHandler,
|
||||
}
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
@@ -58,8 +71,10 @@ func StartDaemon(cfg *AppConfig) error {
|
||||
|
||||
<-sigChan
|
||||
fmt.Println("\n[Daemon] Shutdown signal caught! Initializing graceful teardown sequence...")
|
||||
|
||||
_ = server.Close()
|
||||
ds.shutdownAllRunningServers()
|
||||
|
||||
fmt.Println("[Daemon] All threads gracefully shut down. Exiting supervisor cleanly.")
|
||||
return nil
|
||||
}
|
||||
@@ -185,7 +200,14 @@ func (ds *DaemonServer) handleStart(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err := ds.procManager.StartInstance(name, options.Version, options, ds.cfg)
|
||||
instanceConfigPath := filepath.Join(ds.cfg.Storage.InstancesDir, name, "serverconfig.json")
|
||||
err := SyncInstanceConfig(options.Version, instanceConfigPath, options, ds.cfg)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to sync config: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = ds.procManager.StartInstance(name, options.Version, options, ds.cfg)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Process startup failed: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
|
||||
@@ -21,13 +21,14 @@ type VsServerConfigOptions struct {
|
||||
PreApprovedRole string `json:"PreApprovedRole"`
|
||||
}
|
||||
|
||||
func PrepareInstanceConfig(templateVersion string, instanceConfigPath string, config VsServerConfigOptions, cfg *AppConfig) error {
|
||||
func PrepareInstanceConfig(templateVersion string, instanceConfigPath string, options VsServerConfigOptions, cfg *AppConfig) error {
|
||||
return SyncInstanceConfig(templateVersion, instanceConfigPath, options, cfg)
|
||||
}
|
||||
|
||||
func SyncInstanceConfig(templateVersion string, instanceConfigPath string, options VsServerConfigOptions, cfg *AppConfig) error {
|
||||
templatePath := filepath.Join(cfg.Storage.ConfigTemplatesDir, templateVersion, "serverconfig.json")
|
||||
|
||||
if _, err := os.Stat(instanceConfigPath); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := os.Stat(instanceConfigPath); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(filepath.Dir(instanceConfigPath), 0755); err != nil {
|
||||
return fmt.Errorf("failed creating instance directory tree: %w", err)
|
||||
}
|
||||
@@ -42,17 +43,17 @@ func PrepareInstanceConfig(templateVersion string, instanceConfigPath string, co
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed creating target instance configuration: %w", err)
|
||||
}
|
||||
defer destination.Close()
|
||||
|
||||
if _, err := io.Copy(destination, source); err != nil {
|
||||
_, err = io.Copy(destination, source)
|
||||
destination.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed cloning configuration template payload: %w", err)
|
||||
}
|
||||
|
||||
destination.Close()
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(instanceConfigPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed reading cloned configuration data: %w", err)
|
||||
return fmt.Errorf("failed reading configuration data: %w", err)
|
||||
}
|
||||
|
||||
var rawConfig map[string]interface{}
|
||||
@@ -60,18 +61,17 @@ func PrepareInstanceConfig(templateVersion string, instanceConfigPath string, co
|
||||
return fmt.Errorf("failed parsing configuration JSON payload: %w", err)
|
||||
}
|
||||
|
||||
rawConfig["ServerName"] = config.ServerName
|
||||
rawConfig["Port"] = config.Port
|
||||
rawConfig["MaxClients"] = config.MaxClients
|
||||
rawConfig["ServerName"] = options.ServerName
|
||||
rawConfig["Port"] = options.Port
|
||||
rawConfig["MaxClients"] = options.MaxClients
|
||||
|
||||
if config.Password != "" {
|
||||
rawConfig["Password"] = config.Password
|
||||
if options.Password != "" {
|
||||
rawConfig["Password"] = options.Password
|
||||
} else {
|
||||
rawConfig["Password"] = nil
|
||||
}
|
||||
|
||||
instanceDir := filepath.Dir(instanceConfigPath)
|
||||
|
||||
if worldConfig, ok := rawConfig["WorldConfig"].(map[string]interface{}); ok {
|
||||
worldConfig["SaveFileLocation"] = filepath.Join(instanceDir, "Saves", "default.vcdbs")
|
||||
}
|
||||
@@ -89,9 +89,5 @@ func PrepareInstanceConfig(templateVersion string, instanceConfigPath string, co
|
||||
return fmt.Errorf("failed marshaling updated configuration adjustments: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(instanceConfigPath, updatedData, 0644); err != nil {
|
||||
return fmt.Errorf("failed committing updated configuration to disk: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return os.WriteFile(instanceConfigPath, updatedData, 0644)
|
||||
}
|
||||
|
||||
14
main.go
14
main.go
@@ -19,7 +19,7 @@ func main() {
|
||||
log.Fatalf("Could not locate user home dir: %v", err)
|
||||
}
|
||||
|
||||
configPath := filepath.Join(home, ".config", "vs-manager", "config.json")
|
||||
configPath := filepath.Join(home, ".config", "vssm", "config.json")
|
||||
cfg, err := LoadOrCreateConfig(configPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Initialization failed: %v", err)
|
||||
@@ -139,11 +139,11 @@ func fetchAndPrintStatus(cfg *AppConfig) {
|
||||
}
|
||||
|
||||
func printUsage() {
|
||||
fmt.Println("Vintage Story Server Manager")
|
||||
fmt.Println("VintageStory Server Manager")
|
||||
fmt.Println("\nUsage:")
|
||||
fmt.Println(" go run . daemon Starts the background process supervisor")
|
||||
fmt.Println(" go run . create <name> <version> Provisions baseline configuration and stores instance profile")
|
||||
fmt.Println(" go run . start <name> Launches an existing server instance using stored profile")
|
||||
fmt.Println(" go run . stop <name> Gracefully shuts down a server instance")
|
||||
fmt.Println(" go run . cmd <name> \"<command>\" Dispatches a terminal console command down the pipe")
|
||||
fmt.Println(" vssm daemon Starts the background process supervisor")
|
||||
fmt.Println(" vssm create <name> <version> Provisions baseline configuration and stores instance profile")
|
||||
fmt.Println(" vssm start <name> Launches an existing server instance using stored profile")
|
||||
fmt.Println(" vssm stop <name> Gracefully shuts down a server instance")
|
||||
fmt.Println(" vssm cmd <name> \"<command>\" Dispatches a terminal console command down the pipe")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user