Compare commits

..

7 Commits

Author SHA1 Message Date
283ffb66eb rebuilt command flag system 2026-05-07 17:38:28 -06:00
3d24198ad3 Hotfix
my method of extracting archives does not preserve permission
this has be fixed with a workaround by using chmod to make factorio
executable
2026-05-04 13:11:29 -06:00
9e7bd21602 Added default config if config.yml is missing
Default config:
server:
  serverFolder: "factorio"
  worldFile: "factorio/saves/newworld.zip"
  serverSettings: "factorio/data/server-settings.json"
  serverExec: "factorio/bin/x64/factorio"
  port: 34197

factoryman:
  screen: True
  screenName: "Factorio"
  backupDir: "factorio/backups"
  username: ""
  apitoken: ""
2026-05-04 12:41:18 -06:00
e051d2d6f6 Changed GoConfig to UsrConfig
Changed GoConfig struct to UsrConfig
2026-05-04 11:50:08 -06:00
6ef8f280a8 Added README 2026-05-04 10:02:18 -06:00
cf71d956d2 Minor changes
Spelling error
modified headlessQuery string removing Sprint
2026-05-04 07:35:57 -06:00
bb65f409b4 Better way to download and extract server 2026-05-03 14:25:45 -06:00
13 changed files with 274 additions and 249 deletions

91
README.md Normal file
View File

@@ -0,0 +1,91 @@
# FactoryMan
A simple Factorio server manager for linux systems.
* Download and install headless factorio server (requires api key)
* Download and install mods directly from mod-list.json
* Start and stop factorio in a screen session, great for remote servers
* Backup saves and server
### Commands
```shell
$> ./factoryman download server
```
Run this command to download factorio-headless_linux_latest.
It will install to path in serverFolder in ``config.yml``
*NOTE: username and apitoken are required in ``config.yml``>factoryman*
---
```shell
$> ./factoryman download mod
```
Run this to download mods directly from ``$serverFolder/mods/mod-list.json``
*NOTE: username and apitoken are required in ``config.yml``>factoryman*
---
```shell
$> ./factoryman start
```
Start factorio server (in screen session by default)
---
```shell
$> ./factoryman stop
```
Stop factorio server (in screen session)
---
```shell
$> ./factoryman backup saves
```
Backup saves to path in ``config.yml``>factoryman>backupDir
---
```shell
$> ./factoryman backup full
```
Backup Full serverDir to backupDir
---
*Note:*
use ``$> screen -LS`` to view server terminal
### Simple Config
```config.yml```
```yaml
server:
serverFolder: "factorio"
worldFile: ""
serverSettings: "factorio/data/server-settings.json"
serverExec: "factorio/bin/x64/factorio"
port: 34197
factoryman:
screen: True
screenName: "Factorio"
backupDir: "factorio/backups"
username: ""
apitoken: ""
```
Default config assumes you have used factoryman to download the server

View File

@@ -7,7 +7,7 @@ import (
"time" "time"
) )
func backUp(cmd string, c GoConfig) { func backUp(cmd string, c UsrConfig) {
switch cmd { switch cmd {
case "full": case "full":
fmt.Println("Starting full server backup") fmt.Println("Starting full server backup")

62
cli.go
View File

@@ -1,62 +0,0 @@
package main
import (
"fmt"
"os"
)
func cliToolMode() {
var c = readCfg("config.yml")
modlist := string(c.Server.ServDir) + "/mods/mod-list.json"
if len(os.Args) > 1 {
switch os.Args[1] {
case "start":
if verifyConfig(c) {
startStopServer("start", c)
}
case "stop":
if verifyConfig(c) {
startStopServer("stop", c)
}
case "help", "h", "--help", "-h":
fmt.Printf("Start Server: %s start\nStop Server: %s stop\n", os.Args[0], os.Args[0])
fmt.Printf("Run backup\n\tFull backup: %s backup full", os.Args[0])
fmt.Printf("\n\tBackup saves: %s backup saves\n", os.Args[0])
case "download":
if len(os.Args) > 2 {
switch os.Args[2] {
case "mods":
if !findmodlist(modlist) {
fmt.Printf("FAILED TO FIND MOD LIST")
} else {
fmt.Printf("found %s\n", modlist)
downloadMods(modlist)
}
case "server":
downloadHeadless()
default:
fmt.Println("Invalid mod option: use 'update'")
}
} else {
fmt.Println("Missing mod option: use 'update'")
}
case "backup":
if len(os.Args) > 2 {
switch os.Args[2] {
case "full":
backUp("full", c)
case "saves":
backUp("saves", c)
default:
fmt.Println("Invalid backup type: use 'full' or 'saves'")
}
} else {
fmt.Println("Missing backup type: use 'full' or 'saves'")
}
default:
fmt.Printf("Unknown command: %s. Use 'start', 'stop', or 'backup'.\n", os.Args[1])
}
} else {
fmt.Println("Use 'start', 'stop', or 'backup' command\nex. factoryman start\nTo configure edit 'config.yml'")
}
}

View File

@@ -1,7 +1,6 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"log" "log"
"os" "os"
@@ -9,54 +8,36 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
func readCfg(factCfg string) GoConfig { func DefCfg() *DefConfig {
c := &DefConfig{}
// server settings
c.Server.ServDir = "factorio"
c.Server.ServPort = 34197
c.Server.ServCfg = "factorio/data/server-settings.json"
c.Server.ServExec = "factorio/bin/x64/factorio"
c.Server.WorldFile = "factorio/saves/newworld.zip"
// factoryman settings
c.Factoryman.UseScreen = true
c.Factoryman.ScreenName = "factorio"
c.Factoryman.BackupDir = "factorio/backups"
c.Factoryman.UserName = ""
c.Factoryman.ApiToken = ""
return c
}
func readCfg(factCfg string) UsrConfig {
//read config file (YAML) //read config file (YAML)
fileBytes, err := os.ReadFile(factCfg) fileBytes, err := os.ReadFile(factCfg)
if err != nil { if err != nil {
log.Fatalf("Error reading config file: %v", err) fmt.Printf("Error reading config.yml file, using defaults\n")
return UsrConfig(*DefCfg())
} }
// return Struct // return Struct
var config GoConfig var config UsrConfig
err = yaml.Unmarshal(fileBytes, &config) err = yaml.Unmarshal(fileBytes, &config)
if err != nil { if err != nil {
log.Fatalf("Error unmarshalling YAML file: %v", err) log.Fatalf("Error unmarshalling YAML file: %v", err)
} }
return config return config
} }
func isItReal(path string) bool { //check if path exists
if _, err := os.Stat(path); err == nil {
return true
} else if errors.Is(err, os.ErrNotExist) {
return false
} else {
return false
}
}
func verifyConfig(config GoConfig) bool { // check each file/dir to see if it exists
if !isItReal(config.Server.ServDir) { //check for Server directory
fmt.Printf("PATH NOT FOUND: %s", config.Server.ServDir)
return false
}
if !isItReal(config.Server.WorldFile) { // check for world file
fmt.Printf("PATH NOT FOUND: %s", config.Server.WorldFile)
return false
}
if !isItReal(config.Server.ServCfg) { // check for server config
fmt.Printf("PATH NOT FOUND: %s", config.Server.ServCfg)
return false
}
if !isItReal(config.Server.ServExec) { // check for server exec file to lanch server
fmt.Printf("PATH NOT FOUND: %s", config.Server.ServExec)
return false
}
if !isItReal(config.Factoryman.BackupDir) { // check for backup directory
fmt.Printf("PATH NOT FOUND: %s", config.Factoryman.BackupDir)
return false
}
if config.Factoryman.ApiToken == "" || config.Factoryman.UserName == "" {
fmt.Printf("API Token/Username not set (to download mods add api token and username to config)")
}
return true
}

View File

@@ -1,6 +1,6 @@
server: server:
serverFolder: "factorio" serverFolder: "factorio"
worldFile: "factorio/saves/test.zip" worldFile: "factorio/saves/newworld.zip"
serverSettings: "factorio/data/server-settings.json" serverSettings: "factorio/data/server-settings.json"
serverExec: "factorio/bin/x64/factorio" serverExec: "factorio/bin/x64/factorio"
port: 34197 port: 34197

Binary file not shown.

33
go.mod
View File

@@ -3,36 +3,13 @@ module gitlab.com/Raum0x2A/factoryman
go 1.25.7 go 1.25.7
require ( require (
golift.io/xtractr v0.3.1 github.com/spf13/pflag v1.0.10
github.com/therootcompany/xz v1.0.1
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require ( require (
github.com/Unpackerr/iso9660 v0.0.3 // indirect github.com/kr/pretty v0.3.1 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
github.com/bodgit/sevenzip v1.6.1 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/cavaliergopher/cpio v1.0.1 // indirect
github.com/cavaliergopher/rpm v1.3.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/icza/bitio v1.1.0 // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mewkiz/flac v1.0.13 // indirect
github.com/mewkiz/pkg v0.0.0-20250417130911-3f050ff8c56d // indirect
github.com/mewpkg/term v0.0.0-20241026122259-37a80af23985 // indirect
github.com/nwaples/rardecode/v2 v2.2.2 // indirect
github.com/peterebden/ar v0.0.0-20241106141004-20dc11b778e8 // indirect
github.com/pierrec/lz4/v4 v4.1.26 // indirect
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/sshaman1101/dcompress v0.0.0-20200109162717-50436a6332de // indirect
github.com/therootcompany/xz v1.0.1 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/text v0.34.0 // indirect
golift.io/udf v0.0.1 // indirect
) )

85
go.sum
View File

@@ -1,92 +1,21 @@
github.com/Unpackerr/iso9660 v0.0.3 h1:WXXFIcmDLhnsKhXjPg2moUmHxhoUmIX7FLxrtqHJ7yQ=
github.com/Unpackerr/iso9660 v0.0.3/go.mod h1:4Py6ZWQ+sUVo4BmmzZaFgOLcS3to5BMvH39TlOYNxhA=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4=
github.com/bodgit/sevenzip v1.6.1/go.mod h1:GVoYQbEVbOGT8n2pfqCIMRUaRjQ8F9oSqoBEqZh5fQ8=
github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
github.com/cavaliergopher/rpm v1.3.0 h1:UHX46sasX8MesUXXQ+UbkFLUX4eUWTlEcX8jcnRBIgI=
github.com/cavaliergopher/rpm v1.3.0/go.mod h1:vEumo1vvtrHM1Ov86f6+k8j7zNKOxQfHDCAIcR/36ZI=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/icza/bitio v1.1.0 h1:ysX4vtldjdi3Ygai5m1cWy4oLkhWTAi+SyO6HC8L9T0=
github.com/icza/bitio v1.1.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6 h1:8UsGZ2rr2ksmEru6lToqnXgA8Mz1DP11X4zSJ159C3k=
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mewkiz/flac v1.0.13 h1:6wF8rRQKBFW159Daqx6Ro7K5ZnlVhHUKfS5aTsC4oXs= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/mewkiz/flac v1.0.13/go.mod h1:HfPYDA+oxjyuqMu2V+cyKcxF51KM6incpw5eZXmfA6k= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/mewkiz/pkg v0.0.0-20250417130911-3f050ff8c56d h1:IL2tii4jXLdhCeQN69HNzYYW1kl0meSG0wt5+sLwszU=
github.com/mewkiz/pkg v0.0.0-20250417130911-3f050ff8c56d/go.mod h1:SIpumAnUWSy0q9RzKD3pyH3g1t5vdawUAPcW5tQrUtI=
github.com/mewpkg/term v0.0.0-20241026122259-37a80af23985 h1:h8O1byDZ1uk6RUXMhj1QJU3VXFKXHDZxr4TXRPGeBa8=
github.com/mewpkg/term v0.0.0-20241026122259-37a80af23985/go.mod h1:uiPmbdUbdt1NkGApKl7htQjZ8S7XaGUAVulJUJ9v6q4=
github.com/nwaples/rardecode/v2 v2.2.2 h1:/5oL8dzYivRM/tqX9VcTSWfbpwcbwKG1QtSJr3b3KcU=
github.com/nwaples/rardecode/v2 v2.2.2/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
github.com/peterebden/ar v0.0.0-20241106141004-20dc11b778e8 h1:27L3dHkYbeWGU3/5NasAzVDgXG9QzlfKCvcl4cdNW6c=
github.com/peterebden/ar v0.0.0-20241106141004-20dc11b778e8/go.mod h1:hpFkyhCgB5Rm8FK+ISypOE+9UyrCuL6MNcjPMB1s1ec=
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/sshaman1101/dcompress v0.0.0-20200109162717-50436a6332de h1:uIeuAon/xwRdiZaCmEd5mocquesYkWCf71WBO7obTmA=
github.com/sshaman1101/dcompress v0.0.0-20200109162717-50436a6332de/go.mod h1:XIUpD+1rteMazWrMFjNSpM6TocSHxDYXk6UEgBb5+F0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
go4.org v0.0.0-20260112195520-a5071408f32f h1:ziUVAjmTPwQMBmYR1tbdRFJPtTcQUI12fH9QQjfb0Sw=
go4.org v0.0.0-20260112195520-a5071408f32f/go.mod h1:ZRJnO5ZI4zAwMFp+dS1+V6J6MSyAowhRqAE+DPa1Xp0=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golift.io/udf v0.0.1 h1:kEcJVzqqR+IEWGMuPjuVPT9DzXRDukEgsizKAKn1LF8=
golift.io/udf v0.0.1/go.mod h1:ndK7AlWOh+u+nW9tNsQR95dfHsfASG5Y3dMyzVqmPjw=
golift.io/xtractr v0.3.1 h1:91ZU0DBL3SSfrVYbrENSLUQG4BdHT91rTqkod7TfOUI=
golift.io/xtractr v0.3.1/go.mod h1:X3IQQIiYgwDmAuqeRSBfQqvo6KIWrFzCADTx3yFmt7o=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -7,7 +7,7 @@ import (
"os/exec" "os/exec"
) )
func startStopServer(cmd string, con GoConfig) { func startStopServer(cmd string, con UsrConfig) {
switch cmd { switch cmd {
case "start": case "start":
x := fmt.Sprintf("%s --port %d --server-settings %s --start-server %s", con.Server.ServExec, con.Server.ServPort, con.Server.ServCfg, con.Server.WorldFile) x := fmt.Sprintf("%s --port %d --server-settings %s --start-server %s", con.Server.ServExec, con.Server.ServPort, con.Server.ServCfg, con.Server.WorldFile)

92
main.go
View File

@@ -3,12 +3,96 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
flag "github.com/spf13/pflag"
) )
func main() { func main() {
if len(os.Args) == 1 { var c = readCfg("config.yml")
fmt.Println("Use 'start', 'stop', or 'backup' command\nex. factoryman start\nTo configure edit 'conifg.yml'")
} else { //start command config flags
cliToolMode() startCmd := flag.NewFlagSet("start", flag.ExitOnError)
startScreen := startCmd.BoolP("screen", "s", c.Factoryman.UseScreen, "Start server in screen session")
startServerExec := startCmd.StringP("exec", "e", c.Server.ServExec, "Path to server executable")
startSettings := startCmd.StringP("config", "c", c.Server.ServCfg, "Server config json")
startPort := startCmd.IntP("port", "p", c.Server.ServPort, "Port to start server on")
startScrName := startCmd.StringP("name", "n", c.Factoryman.ScreenName, "Name for screen session")
startWorld := startCmd.StringP("world", "w", c.Server.WorldFile, "World file")
// stop command config flags
stopCmd := flag.NewFlagSet("stop", flag.ExitOnError)
stopScrName := stopCmd.StringP("name", "n", c.Factoryman.ScreenName, "Screen session name")
// download command flags
downloadCmd := flag.NewFlagSet("download", flag.ExitOnError)
downloadSrvLoc := downloadCmd.StringP("path", "d", c.Server.ServDir, "Server Directory path")
downloadAPIKey := downloadCmd.StringP("token", "t", c.Factoryman.ApiToken, "API token")
downloadUserName := downloadCmd.StringP("username", "u", c.Factoryman.UserName, "Factorio username")
// backup command flags
backupCmd := flag.NewFlagSet("backup", flag.ExitOnError)
backupDir := backupCmd.StringP("path", "d", c.Factoryman.BackupDir, "Path to backup directory")
if len(os.Args) < 2 {
fmt.Println("Expected subcommand. \n\tex.) start, stop, download")
os.Exit(1)
}
switch os.Args[1] {
case "start":
fmt.Println("starting server")
startCmd.Parse(os.Args[2:])
//map user input settings to config struct
c.Factoryman.UseScreen = *startScreen
c.Server.ServExec = *startServerExec
c.Server.ServCfg = *startSettings
c.Server.ServPort = *startPort
c.Factoryman.ScreenName = *startScrName
c.Server.WorldFile = *startWorld
startStopServer("start", c)
case "stop":
fmt.Println("stopping server")
stopCmd.Parse(os.Args[2:])
c.Factoryman.ScreenName = *stopScrName
startStopServer("stop", c)
case "download":
downloadCmd.Parse(os.Args[2:])
c.Server.ServDir = *downloadSrvLoc
c.Factoryman.ApiToken = *downloadAPIKey
c.Factoryman.UserName = *downloadUserName
switch os.Args[2] {
case "mods":
fmt.Println("Processing mod-list.json")
downloadMods(c)
case "server":
fmt.Println("starting download")
downloadHeadless(c)
default:
fmt.Println("mods or server")
os.Exit(1)
}
case "backup":
backupCmd.Parse(os.Args[2:])
c.Factoryman.BackupDir = *backupDir
switch os.Args[2] {
case "saves":
backUp("saves", c)
case "full":
backUp("full", c)
default:
fmt.Println("saves or full")
os.Exit(1)
}
case "dev":
fmt.Println("=)")
default:
flag.Usage()
os.Exit(1)
} }
} }

13
mods.go
View File

@@ -8,16 +8,9 @@ import (
"os" "os"
) )
func findmodlist(modlist string) bool { func downloadMods(c UsrConfig) {
if !isItReal(modlist) { //var c = readCfg("config.yml")
return false modlist := string(c.Server.ServDir) + "/mods/mod-list.json"
} else {
return true
}
}
func downloadMods(modlist string) {
var c = readCfg("config.yml")
fmt.Printf("Updating mods from %s\n", modlist) fmt.Printf("Updating mods from %s\n", modlist)

View File

@@ -1,35 +1,49 @@
package main package main
import ( import (
"fmt" "archive/tar"
"io"
"log" "log"
"net/http"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"github.com/therootcompany/xz"
) )
func downloadHeadless() { func downloadHeadless(c UsrConfig) {
var c = readCfg("config.yml") //var c = readCfg("config.yml")
headlessQuery := fmt.Sprintf("https://www.factorio.com/get-download/latest/headless/linux64?username=%s&token=%s", c.Factoryman.UserName, c.Factoryman.ApiToken) headlessQuery := "https://www.factorio.com/get-download/latest/headless/linux64"
dlerr := download("./factorio.tar.xz", headlessQuery) url := headlessQuery
if dlerr != nil { resp, err := http.Get(url)
log.Fatalf("Unable to download: %v", dlerr) if err != nil || resp.StatusCode != http.StatusOK {
log.Fatal("Download failed")
}
defer resp.Body.Close()
// Wrap stream in XZ and Tar readers
xzReader, _ := xz.NewReader(resp.Body, 0)
tr := tar.NewReader(xzReader)
for {
header, err := tr.Next()
if err == io.EOF {
break
} }
err := os.MkdirAll(c.Server.ServDir, os.ModePerm) target := filepath.Join(".", header.Name)
if err != nil { switch header.Typeflag {
fmt.Printf("Error creating directory: %v\n", err) case tar.TypeDir:
return os.MkdirAll(target, 0755)
case tar.TypeReg:
os.MkdirAll(filepath.Dir(target), 0755)
outFile, _ := os.Create(target)
io.Copy(outFile, tr)
outFile.Close()
} }
fmt.Println("tar", "-xf", "factorio.tar.xz", "-C", c.Server.ServDir+"/")
cmd := exec.Command("tar", "-xf", "factorio.tar.xz", "-C")
// 2. Run and capture Standard Output
out, err := cmd.Output()
if err != nil {
log.Fatal(err)
} }
fmt.Println(err) // zip does not preserve permissions correctly so i have to do this
fmt.Println(string(out)) exec.Command("chmod", "+x", c.Server.ServExec)
} }

View File

@@ -1,7 +1,7 @@
package main package main
// config struct // config struct
type GoConfig struct { type UsrConfig struct {
Server struct { // server specific settings Server struct { // server specific settings
ServDir string `yaml:"serverFolder"` ServDir string `yaml:"serverFolder"`
WorldFile string `yaml:"worldFile"` WorldFile string `yaml:"worldFile"`
@@ -19,6 +19,24 @@ type GoConfig struct {
} `yaml:"factoryman"` } `yaml:"factoryman"`
} }
type DefConfig struct {
Server struct { // server specific settings
ServDir string
WorldFile string
ServCfg string
ServExec string
ServPort int
}
Factoryman struct { // factoryman settings
BackupDir string
UseScreen bool
ScreenName string
UserName string
ApiToken string
}
}
// JSON file from config // JSON file from config
type Mod struct { type Mod struct {
Name string `json:"name"` Name string `json:"name"`