Skip to content

Commit ec79076

Browse files
re-add autostart and symlink setup for systemd. Adds the ability to run ssui with -setupautostart to install and configure a systemd service that runs the latest SSUI, even if the executable changes in updates
Co-authored-by: akirilov <atanas_kirilov@hotmail.com>
1 parent ae5bacd commit ec79076

7 files changed

Lines changed: 242 additions & 11 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ UIMod/config/config.json
3838
C:/custom/file.txt
3939
UIMod/config/customdetections.json
4040
winhttp.dll
41-
autostart*
41+
./autostart.sh
42+
./autostart.ps1
4243
__debug_bin*
4344
frontend/node_modules
4445
frontend/dist

src/core/loader/cmdargs.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ package loader
33
import (
44
"flag"
55
"fmt"
6+
"os"
7+
"runtime"
68
"strings"
79
"time"
810

911
"github.com/JacksonTheMaster/StationeersServerUI/v5/src/config"
1012
"github.com/JacksonTheMaster/StationeersServerUI/v5/src/core/security"
1113
"github.com/JacksonTheMaster/StationeersServerUI/v5/src/logger"
14+
"github.com/JacksonTheMaster/StationeersServerUI/v5/src/setup/autostart"
1215
)
1316

1417
// Define flags matching the config variable names
@@ -22,6 +25,7 @@ var devModeFlag bool
2225
var skipSteamCMDFlag bool
2326
var sanityCheckFlag bool
2427
var overrideAdvertisedIpFlag string
28+
var autoStartSSUIOnBoot bool
2529

2630
// ParseFlags parses command-line arguments ONCE at startup (called from func main)
2731
func ParseFlags() {
@@ -41,6 +45,7 @@ func ParseFlags() {
4145
flag.BoolVar(&skipSteamCMDFlag, "NoSteamCMD", false, "Skips SteamCMD installation")
4246
flag.BoolVar(&sanityCheckFlag, "NoSanityCheck", false, "Skips the sanity check. Not recommended.")
4347
flag.StringVar(&overrideAdvertisedIpFlag, "OverrideAdvertisedIp", "", "Override the advertised server IP (to allow server advertisement if you are behind a reverse proxy)")
48+
flag.BoolVar(&autoStartSSUIOnBoot, "setupautostart", false, "Setup Auto-start SSUI on boot")
4449

4550
// Parse command-line flags
4651
flag.Parse()
@@ -62,6 +67,20 @@ func HandleFlags() {
6267
config.SetSkipSteamCMD(true)
6368
}
6469

70+
if autoStartSSUIOnBoot {
71+
if runtime.GOOS != "linux" {
72+
logger.Core.Error("Autostart is only supported on Linux. Exiting in 5 seconds...")
73+
time.Sleep(5 * time.Second)
74+
os.Exit(0)
75+
}
76+
err := autostart.Initialize()
77+
if err != nil {
78+
logger.Core.Error("Failed to initialize autostart: " + err.Error())
79+
}
80+
time.Sleep(5 * time.Second)
81+
os.Exit(0)
82+
}
83+
6584
if backendEndpointPortFlag != "" && backendEndpointPortFlag != "8443" {
6685
oldPort := config.GetSSUIWebPort()
6786
config.SetSSUIWebPort(backendEndpointPortFlag)

src/setup/autostart/autostart.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package autostart
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
func Initialize() error {
8+
9+
err := SetupAutostartScripts()
10+
if err != nil {
11+
return fmt.Errorf("failed to setup autostart script, cannot proceed with autostart setup: %w", err)
12+
}
13+
14+
err = SetupBinarySymlink()
15+
if err != nil {
16+
return fmt.Errorf("failed to create symlink for autostart: %w", err)
17+
}
18+
19+
err = SetupService()
20+
if err != nil {
21+
return fmt.Errorf("failed to setup service: %w", err)
22+
}
23+
return nil
24+
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
package setup
1+
package autostart
22

33
import (
4+
"fmt"
45
"io"
56
"io/fs"
67
"os"
@@ -9,40 +10,58 @@ import (
910
"github.com/JacksonTheMaster/StationeersServerUI/v5/src/config"
1011
)
1112

12-
func SetupAutostartScripts() {
13+
func SetupAutostartScripts() error {
1314
scriptFS, err := fs.Sub(config.V1UIFS, "UIMod/onboard_bundled/scripts")
1415
if err != nil {
15-
return
16+
return err
1617
}
1718

1819
if runtime.GOOS == "windows" {
1920
script, err := scriptFS.Open("autostart.ps1")
2021
if err != nil {
21-
return
22+
return err
2223
}
2324
defer script.Close()
2425
data, err := io.ReadAll(script)
2526
if err != nil {
26-
return
27+
return err
2728
}
29+
30+
_, err = os.Stat("autostart.ps1")
31+
if err == nil {
32+
err = os.Remove("autostart.ps1")
33+
if err != nil {
34+
return fmt.Errorf("failed to remove autostart.ps1: %w", err)
35+
}
36+
}
37+
2838
err = os.WriteFile("autostart.ps1", data, 0755)
2939
if err != nil {
30-
return
40+
return err
3141
}
3242
}
3343
if runtime.GOOS == "linux" {
3444
script, err := scriptFS.Open("autostart.sh")
3545
if err != nil {
36-
return
46+
return err
3747
}
3848
defer script.Close()
3949
data, err := io.ReadAll(script)
4050
if err != nil {
41-
return
51+
return err
4252
}
43-
err = os.WriteFile("autostart.service", data, 0755)
53+
_, err = os.Stat("autostart.sh")
54+
if err == nil {
55+
err = os.Remove("autostart.sh")
56+
if err != nil {
57+
return fmt.Errorf("failed to remove autostart.sh: %w", err)
58+
}
59+
}
60+
61+
err = os.WriteFile("autostart.sh", data, 0755)
4462
if err != nil {
45-
return
63+
return err
4664
}
4765
}
66+
return nil
4867
}

src/setup/autostart/service-lin.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//go:build linux
2+
3+
package autostart
4+
5+
import (
6+
"fmt"
7+
"os"
8+
"os/exec"
9+
"time"
10+
11+
"github.com/JacksonTheMaster/StationeersServerUI/v5/src/logger"
12+
)
13+
14+
func SetupService() error {
15+
// run ./autostart.sh, which will ask for elevation when needed and setup the service
16+
17+
//stat /etc/systemd/system/ssui.service and it it exists tell the user that the service is already installed
18+
_, err := os.Stat("/etc/systemd/system/stationeersserverui.service")
19+
if err == nil {
20+
logger.Main.Error("Autostart service is already installed. Exiting in 5 seconds...")
21+
time.Sleep(5 * time.Second)
22+
os.Exit(1)
23+
}
24+
25+
exec.Command("chmod", "+x", "autostart.sh").Run()
26+
exec.Command("./autostart.sh", "install").Run()
27+
28+
err = exec.Command("chmod", "+x", "autostart.sh").Run()
29+
if err != nil {
30+
return fmt.Errorf("failed to chmod autostart.sh: %w", err)
31+
}
32+
cmd := exec.Command("./autostart.sh")
33+
err = cmd.Run()
34+
if err != nil {
35+
if exitErr, ok := err.(*exec.ExitError); ok {
36+
// handle exit codes in a switch statement
37+
switch exitErr.ExitCode() {
38+
case 10:
39+
return fmt.Errorf("autostart.sh failed: For security reasons, it is not recommended to run this service as a root user")
40+
case 2:
41+
return fmt.Errorf("autostart.sh Error: systemd is not available on this system")
42+
case 3:
43+
return fmt.Errorf("autostart.sh Error: Could not determine base directory")
44+
case 4:
45+
return fmt.Errorf("autostart.sh Failed to stop ssui.service")
46+
case 5:
47+
return fmt.Errorf("autostart.sh Error: Failed to create service file")
48+
case 6:
49+
return fmt.Errorf("autostart.sh Error: Failed to reload systemd daemon")
50+
case 7:
51+
return fmt.Errorf("autostart.sh Error: Failed to enable ssui.service")
52+
case 8:
53+
return fmt.Errorf("autostart.sh Error: Failed to start ssui.service")
54+
default:
55+
return fmt.Errorf("autostart.sh failed with exit code %d: %w", exitErr.ExitCode(), err)
56+
}
57+
}
58+
59+
return fmt.Errorf("failed to run autostart.sh: %w", err)
60+
}
61+
// stat for the file to check if it exists, if it does, remove it
62+
_, err = os.Stat("autostart.sh")
63+
if err == nil {
64+
err = os.Remove("autostart.sh")
65+
if err != nil {
66+
return fmt.Errorf("failed to remove autostart.sh: %w", err)
67+
}
68+
}
69+
logger.Main.Info("Autostart setup complete. Restart SSUI without the --setupautostart flag to start normally. Exiting in 5 seconds...")
70+
return nil
71+
72+
}

src/setup/autostart/service-win.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//go:build windows
2+
3+
package autostart
4+
5+
func SetupService() error {
6+
//TODO: Windows implementation
7+
return nil
8+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package autostart
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
"runtime"
9+
"strings"
10+
11+
"github.com/JacksonTheMaster/StationeersServerUI/v5/src/config"
12+
"github.com/JacksonTheMaster/StationeersServerUI/v5/src/logger"
13+
)
14+
15+
func SetupBinarySymlink() error {
16+
if runtime.GOOS != "linux" {
17+
return fmt.Errorf("not on linux")
18+
}
19+
// Check if we are inside a docker container, if so, don't try to create a symlink
20+
if IsInsideContainer() {
21+
logger.Install.Info("Inside a container, skipping symlink creation.")
22+
return fmt.Errorf("inside a container")
23+
}
24+
25+
if err := updateSymlink(); err != nil {
26+
return err
27+
}
28+
return nil
29+
}
30+
31+
func updateSymlink() error {
32+
// Get the absolute path of the current executable
33+
executablePath, err := os.Executable()
34+
if err != nil {
35+
return err
36+
}
37+
38+
// Get the directory of the executable
39+
executableDir := filepath.Dir(executablePath)
40+
41+
// Construct the target filename (e.g., StationeersServerControl5.0.x86_64)
42+
currentExecutableName := "StationeersServerControlv" + config.GetVersion() + ".x86_64"
43+
44+
// Construct the full path to the target executable
45+
targetPath := filepath.Join(executableDir, currentExecutableName)
46+
47+
symlinkPath := filepath.Join(executableDir, "StationeersServerUI.lnk")
48+
49+
// Remove existing symlink if it exists
50+
if err := os.Remove(symlinkPath); err != nil && !os.IsNotExist(err) {
51+
return err
52+
}
53+
54+
// Create the symlink
55+
return os.Symlink(targetPath, symlinkPath)
56+
}
57+
58+
func IsInsideContainer() bool {
59+
// Check .dockerenv file (Docker-specific)
60+
if _, err := os.Stat("/.dockerenv"); err == nil {
61+
return true
62+
}
63+
64+
// Check cgroup (works for Docker and other container runtimes)
65+
return isContainerFromCGroup()
66+
}
67+
68+
func isContainerFromCGroup() bool {
69+
file, err := os.Open("/proc/1/cgroup")
70+
if err != nil {
71+
return false
72+
}
73+
defer file.Close()
74+
75+
scanner := bufio.NewScanner(file)
76+
for scanner.Scan() {
77+
line := scanner.Text()
78+
// Check for various container runtime indicators
79+
if strings.Contains(line, "docker") ||
80+
strings.Contains(line, "containerd") ||
81+
strings.Contains(line, "kubepods") ||
82+
strings.Contains(line, "crio") ||
83+
strings.Contains(line, "libpod") {
84+
return true
85+
}
86+
}
87+
return false
88+
}

0 commit comments

Comments
 (0)