Skip to content
This repository has been archived by the owner on Mar 16, 2024. It is now read-only.

Commit

Permalink
Update ark server before starting it and embed steamcmd and more (#55)
Browse files Browse the repository at this point in the history
* embedded steamcmd

* add server auto updater

* refactored console

* added stop functionality

* added error feedback on server save

* removed useless .then
  • Loading branch information
JensvandeWiel authored Nov 2, 2023
1 parent 5820e0c commit 1756399
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 20 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,5 @@ fabric.properties
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser

!installer/steamcmd.exe
steamcmd
6 changes: 2 additions & 4 deletions frontend/src/pages/InstallUpdater.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from "@mui/joy";
import {OpenDirectoryDialog} from "../../wailsjs/go/helpers/HelpersController";
import {IconDownload} from "@tabler/icons-react";
import {Install} from "../../wailsjs/go/installer/InstallerController";
import {InstallUpdateVerify} from "../../wailsjs/go/installer/InstallerController";
import {EventsOn} from "../../wailsjs/runtime";
import {useAlert} from "../components/AlertProvider";

Expand All @@ -40,13 +40,11 @@ export function InstallUpdater({setServ, serv, onInstalled}: Props) {
return
}
setInstallerModalOpen(true)
Install(serv.serverPath).catch((err) => {
InstallUpdateVerify(serv.serverPath).catch((err) => {
setAction("failed installing: " + err.message);
setInstallerModalOpen(false);
console.error(err);
addAlert("Installer failed: " + err, "danger")


})
}

Expand Down
38 changes: 36 additions & 2 deletions frontend/src/pages/Server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ import {useAlert} from "../components/AlertProvider";
import {BrowserOpenURL, EventsOff, EventsOn} from "../../wailsjs/runtime";
import {IconAlertCircleFilled, IconExternalLink} from "@tabler/icons-react";
import {Console} from "./server/Console";
import {UpdaterModal} from "./UpdaterModal";
import {InstallUpdateVerify} from "../../wailsjs/go/installer/InstallerController";
import {SendRconCommand} from "../../wailsjs/go/helpers/HelpersController";


type Props = {
Expand All @@ -48,6 +51,7 @@ export const Server = ({id, className}: Props) => {
const [isInstalled, setIsInstalled] = useState(false)
const [serverStatus, setServerStatus] = useState(false)
const [forceStopModalOpen, setForceStopModalOpen] = useState(false)
const [updaterModalOpen, setUpdaterModalOpen] = useState(false)
const {addAlert} = useAlert()

//region useEffect land :)
Expand All @@ -66,7 +70,7 @@ export const Server = ({id, className}: Props) => {

useEffect(() => {
if (serv.id >= 0) {
SaveServer(serv).catch((reason) => console.error(reason))
SaveServer(serv).catch((reason) => {console.error(reason); addAlert(reason, "danger")})
}
}, [serv]);

Expand All @@ -84,10 +88,39 @@ export const Server = ({id, className}: Props) => {
//endregion

function onServerStartButtonClicked() {

if (serv.serverPath == "") {
addAlert("Server Path must be set to a path", "warning")
return
}

setUpdaterModalOpen(true)
InstallUpdateVerify(serv.serverPath).catch((err) => {
addAlert("failed installing: " + err.message, "danger");
setUpdaterModalOpen(false);
console.error(err);
}).then(() => {
setUpdaterModalOpen(false);
startServer()
})

}

function startServer() {
StartServer(serv.id).catch((err) => {addAlert(err, "danger"); console.error(err)}).then(() => setTimeout(function () {
refreshServerStatus()
}, 200))
}

function onServerStopButtonClicked() {
addAlert("Stopping server...", "neutral")
SendRconCommand("saveworld", serv.ipAddress, serv.rconPort, serv.adminPassword)
.then((resp) => {
//send quit command
SendRconCommand("doexit", serv.ipAddress, serv.rconPort, serv.adminPassword)
.catch((err) => addAlert("error sending exit command: " + err, "danger"));
})
.catch((err) => addAlert("error sending save command: " + err, "danger"));
}

function onServerForceStopButtonClicked() {
Expand Down Expand Up @@ -119,9 +152,10 @@ export const Server = ({id, className}: Props) => {
<div className={'ml-auto my-auto mr-8'}>
<ButtonGroup aria-label="outlined primary button group">
<Button color={'success'} variant="solid" disabled={serverStatus} onClick={onServerStartButtonClicked}>Start</Button>
<Button color={'danger'} variant="solid" disabled={/*!serverStatus*/ true} onClick={onServerStartButtonClicked}>Stop</Button>
<Button color={'danger'} variant="solid" disabled={!serverStatus} onClick={onServerStopButtonClicked}>Stop</Button>
<Button color={'danger'} variant="solid" disabled={!serverStatus} onClick={() => setForceStopModalOpen(true)}>Force stop</Button>
</ButtonGroup>
<UpdaterModal open={updaterModalOpen} onClose={() => setUpdaterModalOpen(false)}></UpdaterModal>
<Modal open={forceStopModalOpen} onClose={() => setForceStopModalOpen(false)}>
<ModalDialog variant="outlined" role="alertdialog">
<DialogTitle>
Expand Down
56 changes: 56 additions & 0 deletions frontend/src/pages/UpdaterModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {Button, Divider, LinearProgress, Modal, ModalClose, ModalDialog, Typography} from "@mui/joy";
import React, {useEffect, useState} from "react";
import {server} from "../../wailsjs/go/models";
import {useAlert} from "../components/AlertProvider";
import {EventsOn} from "../../wailsjs/runtime";


type Props = {
onCompleted?: () => void;
open: boolean;
onClose?: () => void;
}

export function UpdaterModal({onCompleted, open, onClose}: Props) {

const [action, setAction] = useState("Preparing")
const [progress, setProgress] = useState(0.00)
const [isCompleted, setIsCompleted] = useState(false)

useEffect(() => {
EventsOn("installingUpdateAction", (data) => {setAction(data);})
EventsOn("installingUpdateProgress", (data) => {setProgress(data);})
EventsOn("appInstalled", (i) => {setIsCompleted(true); setAction("Preparing"); setProgress(0.00)})
}, []);

useEffect(() => {
if (isCompleted && onCompleted) {
onCompleted()

}
}, [isCompleted]);

return (
<Modal open={open} onClose={onClose} >
<ModalDialog>
<ModalClose/>
<Typography level="title-md">
Updating server...
</Typography>
<Typography level="body-sm" >
The server will start automatically after the update
</Typography>
<Divider className={'mx-2'}/>
<Typography fontWeight={700} level="title-md">
Status: {action}
</Typography>
<Typography fontWeight={700} level="title-md">
Progress:
</Typography>
<div className={'w-1/2 mt-4'}>
<LinearProgress determinate value={progress} />
</div>
</ModalDialog>
</Modal>
);
}
49 changes: 37 additions & 12 deletions frontend/src/pages/server/Console.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Button, Input, TabPanel } from "@mui/joy";
import {Button, IconButton, Input, TabPanel, Tooltip} from "@mui/joy";
import React, { useEffect, useRef, useState } from "react";
import { server } from "../../../wailsjs/go/models";
import { SendRconCommand } from "../../../wailsjs/go/helpers/HelpersController";
import {IconEraser, IconSend} from "@tabler/icons-react";

type Message = {
text: string;
Expand Down Expand Up @@ -37,6 +38,16 @@ export function Console({ setServ, serv, serverStatus }: Props) {
}
}, [messages]);

useEffect(() => {
if (!serverStatus) {
setMessages([])
}
}, [serverStatus]);

useEffect(() => {
setMessages([])
}, [serv.id]);

if (serverStatus) {
return (
<TabPanel value={0}>
Expand Down Expand Up @@ -64,17 +75,31 @@ export function Console({ setServ, serv, serverStatus }: Props) {
onChange={(e) => setInput(e.target.value)}
startDecorator={<span className={"text-green-400"}>$</span>}
endDecorator={
<Button
color={"neutral"}
onClick={(e) => {
writeToConsole(input);
setInput("");
doRconCommand(input);
}}
className={"m-1"}
>
Send
</Button>
<span>
<Tooltip title={"send message"}>
<IconButton
color={"neutral"}
onClick={(e) => {
writeToConsole(input);
setInput("");
doRconCommand(input);
}}
className={"m-1"}
>
<IconSend/>
</IconButton>
</Tooltip>
<Tooltip title={"clear input"}>
<IconButton
color={"neutral"}
onClick={(e) => {
setMessages([])
}}
className={"m-1"}>
<IconEraser/>
</IconButton>
</Tooltip>
</span>
}
onKeyPress={(e) => {
if (e.key === "Enter") {
Expand Down
47 changes: 47 additions & 0 deletions installer/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package installer

import (
_ "embed"
"fmt"
"os"
"path"
)

//go:embed steamcmd.exe
var steamExe []byte

func (c *InstallerController) setupSteamCMD() error {

exePath, err := os.Executable()
if err != nil {
return fmt.Errorf("failed to get executable directory: %v", err)
}

steamExePath := path.Join(path.Dir(exePath), "steamcmd", "steamcmd.exe")

if _, err := os.Stat(path.Join(steamExePath)); os.IsNotExist(err) {
//steamcmd is not installed

err := os.MkdirAll(path.Join(path.Dir(steamExePath)), 0666)
if err != nil {
return fmt.Errorf("failed to create steamcmd directory: %v", err)
}

err = os.WriteFile(steamExePath, steamExe, 0666)
if err != nil {
return fmt.Errorf("failed to create steamcmd directory: %v", err)
}

c.config.GetConfig()
c.config.Config.SteamCMDPath = path.Join(path.Dir(exePath), "steamcmd", "steamcmd.exe")
c.config.SaveConfig()

} else {
//steamCMD is installed so set config to the steamCMD path
c.config.GetConfig()
c.config.Config.SteamCMDPath = path.Join(path.Dir(exePath), "steamcmd", "steamcmd.exe")
c.config.SaveConfig()
}

return nil
}
14 changes: 12 additions & 2 deletions installer/installer_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,22 @@ Events:
*/

// Install installs the server and returns true is successful and error and false if failed
func (c *InstallerController) Install(installPath string) error {
// InstallUpdateVerify installs/updates/verifies the server and returns true is successful and error and false if failed
func (c *InstallerController) InstallUpdateVerify(installPath string) error {

//get steamcmd path
c.config.GetConfig()
steamCMDPath := c.config.Config.SteamCMDPath
// if steamCMDPath is not set then get the bundled steamcmd
if steamCMDPath == "" {
err := c.setupSteamCMD()
if err != nil {
return fmt.Errorf("Failed to prepare SteamCMD: " + err.Error())
}
}

c.config.GetConfig()
steamCMDPath = c.config.Config.SteamCMDPath

prompts := []*gosteamcmd.Prompt{
gosteamcmd.ForceInstallDir("\"" + installPath + "\""),
Expand Down
Binary file added installer/steamcmd.exe
Binary file not shown.

0 comments on commit 1756399

Please sign in to comment.