mirror of
https://ceregatti.org/git/daniel/dayzdockerserver.git
synced 2025-05-06 14:21:18 +00:00
Continued work on the web UI:
XML "editor" WIP. Add xmltree component. Start work on shelling out to steamcmd from node. Move script functions out of common and into individual scripts. Update docs.
This commit is contained in:
parent
052304f450
commit
cf47b7fe1f
9 changed files with 389 additions and 231 deletions
95
README.md
95
README.md
|
@ -1,32 +1,26 @@
|
|||
# DayZDockerServer
|
||||
|
||||
A Linux [DayZ](https://dayz.com) server in a [Docker](https://docs.docker.com/) container. The main script's
|
||||
functionality is derived from [this project](https://github.com/thelastnoc/dayz-sa_linuxserver). That functionality is
|
||||
described [here](https://steamcommunity.com/sharedfiles/filedetails/?id=1517338673). The goal is to reproduce some of
|
||||
that functionality but also add more features.
|
||||
A Linux [DayZ](https://dayz.com) server in a [Docker](https://docs.docker.com/) container. The main script's functionality is derived from [this project](https://github.com/thelastnoc/dayz-sa_linuxserver). That functionality is described [here](https://steamcommunity.com/sharedfiles/filedetails/?id=1517338673). The goal is to reproduce some of that functionality but also add more features.
|
||||
|
||||
The main goal is to provide a turnkey DayZ server with mod support that can be spun up with as little as a machine running Linux with Docker and Docker Compose installed.
|
||||
|
||||
## Caveat Emptor
|
||||
|
||||
As of DayZ release 1.15, a [Linux DayZ server](https://steamdb.info/app/1042420/) was made available in Dayz
|
||||
Experimental. This has not been officially released, so this will only run a DayZ Experimental server at the
|
||||
moment. Only the [DayZ Experimental client](https://dayz.fandom.com/wiki/Experimental) will be able to connect to it.
|
||||
The goal is to have a working implementation once the Linux server is officially released, presumably
|
||||
[here](https://steamdb.info/app/223350/).
|
||||
As of DayZ release 1.15, a [Linux DayZ server](https://steamdb.info/app/1042420/) was made available in Dayz Experimental. This has not been officially released, so this will only run a DayZ Experimental server at the moment. Only the [DayZ Experimental client](https://dayz.fandom.com/wiki/Experimental) will be able to connect to it. The goal is to have a working implementation once the Linux server is officially released, presumably [here](https://steamdb.info/app/223350/).
|
||||
|
||||
This process will create a docker volume for the unprivileged user's home directory, which stores the DayZ server files.
|
||||
This volume can get quite large. It will require at least 2G of disk space for the default install. Much more with mods.
|
||||
Some map mods are as large as 10G. Make sure you have that much disk space in the location where docker stores its
|
||||
volumes, usually `/var/lib/docker/volumes`.
|
||||
This process will create several docker volumes for the following sets of files:
|
||||
* serverfiles: The base server files
|
||||
* mpmissions: The mpmissions directory of the base server files.
|
||||
* mods: All mods will be stored here
|
||||
* homedir: The user home directory. Each container gets its own. Where [SteamCMD](https://developer.valvesoftware.com/wiki/SteamCMD) keeps its resource files.
|
||||
* profiles: A running server's profile. A server container gets its own volume.
|
||||
* servermpmissions: A running -server's mpmissions directory. A server container gets its own volume.
|
||||
|
||||
## Goals
|
||||
|
||||
* Provide a turnkey DayZ server with mod support.
|
||||
These volumes can get quite large. The `serverfiles` one will require at least 2.7G of disk space for the default install. The mods one can fill up very quickly, with some map mods being as large as 10G. Make sure you have that much disk space in the location where docker stores its volumes, usually `/var/lib/docker/volumes`.
|
||||
|
||||
## Configure and Build
|
||||
|
||||
Ensure [Docker](https://docs.docker.com/engine/install/) and [Docker compose](https://docs.docker.com/compose/install/)
|
||||
are properly installed. This means setting it up so it runs as your user. Make sure you're running these commands as
|
||||
your user, in your home directory.
|
||||
Ensure [Docker](https://docs.docker.com/engine/install/) and [Docker compose](https://docs.docker.com/compose/install/) are properly installed. This means setting it up so it runs as your user. Make sure you're running these commands as your user, in your home directory.
|
||||
|
||||
Clone the repo, and change into the newly created directory:
|
||||
|
||||
|
@ -35,14 +29,7 @@ git clone https://ceregatti.org/git/daniel/dayzdockerserver.git
|
|||
cd dayzdockerserver
|
||||
```
|
||||
|
||||
Edit `files/serverDZ.cfg` and set the values of any variables there.
|
||||
See the [documentation](https://forums.dayz.com/topic/239635-dayz-server-files-documentation/):
|
||||
|
||||
```
|
||||
hostname = "Something other than Server Name"; // Server name
|
||||
```
|
||||
|
||||
Build the Docker image:
|
||||
Build the Docker images:
|
||||
|
||||
```
|
||||
docker compose build
|
||||
|
@ -50,40 +37,38 @@ docker compose build
|
|||
|
||||
### Steam Integration
|
||||
|
||||
[SteamCMD](https://developer.valvesoftware.com/wiki/SteamCMD) is used to manage Steam downloads. A vanilla DayZ server
|
||||
can be installed with the `anonymous` Steam user, but most mods cannot. If the goal is to add mods, a real Steam login
|
||||
must be used. Login:
|
||||
[SteamCMD](https://developer.valvesoftware.com/wiki/SteamCMD) is used to manage Steam downloads. A vanilla DayZ server can be installed with the `anonymous` Steam user, but most mods cannot. If the goal is to add mods, a real Steam login must be used. Login:
|
||||
|
||||
```
|
||||
docker compose run --rm main dayzserver login
|
||||
docker compose run --rm web dz login
|
||||
```
|
||||
|
||||
Follow the prompts. Hit enter to accept the default, which is to use the `anonymous` user, otherwise use your real
|
||||
username and keep following the prompts to add your password and Steam Guard code. With Steam Guard enabled on the Steam
|
||||
account, entering the password will trigger the sending of an email with the code. This process will wait indefinitely
|
||||
until the code is entered.
|
||||
Follow the prompts. Hit enter to accept the default, which is to use the `anonymous` user, otherwise use your real username and keep following the prompts to add your password and Steam Guard code. This process will wait indefinitely until the code is entered.
|
||||
|
||||
The credentials will be managed by [SteamCMD](https://developer.valvesoftware.com/wiki/SteamCMD). How it encrypts or
|
||||
otherwise obfuscates the credentials is beyond the scope of this document. Suffice to say that they are stored in the
|
||||
docker volume. All subsequent SteamCMD commands will use the stored credentials. so this process does not need to be
|
||||
repeated unless the session expires or the docker volume is deleted.
|
||||
The credentials will be managed by [SteamCMD](https://developer.valvesoftware.com/wiki/SteamCMD). How it encrypts or otherwise obfuscates the credentials is beyond the scope of this document. Suffice to say that they are stored in the docker volume. All subsequent SteamCMD commands will use the stored credentials. so this process does not need to be repeated unless the session expires or the docker volume is deleted.
|
||||
|
||||
Run the command again to manage the login. See [Manage](#manage).
|
||||
To manage the login credentials, simply run the command again. See [Manage](#manage).
|
||||
|
||||
## Install
|
||||
|
||||
The server files must be installed before the server can be run:
|
||||
```
|
||||
docker compose run --rm main dayzserver install
|
||||
docker compose run --rm web dz install
|
||||
```
|
||||
This process will download the several gigabyes of files required to run the server.
|
||||
This process will download the several gigabytes (about 2.7G) of files required to run the server.
|
||||
|
||||
## Run
|
||||
|
||||
Launch the container into the background:
|
||||
Edit `files/serverDZ.cfg` and set the values of any variables there. See the [documentation](https://forums.dayz.com/topic/239635-dayz-server-files-documentation/):
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
hostname = "Something other than Server Name"; // Server name
|
||||
```
|
||||
|
||||
Launch the server container into the background:
|
||||
|
||||
```
|
||||
docker compose up -d server
|
||||
```
|
||||
|
||||
Tail the log:
|
||||
|
@ -95,7 +80,7 @@ docker compose logs -f
|
|||
|
||||
To stop the DayZ server:
|
||||
```
|
||||
docker compose exec main dz stop
|
||||
docker compose exec server dz stop
|
||||
```
|
||||
|
||||
If it exits cleanly, the container will also stop. Otherwise the server will restart
|
||||
|
@ -115,14 +100,14 @@ A terminal-based RCON client is included: https://github.com/indepth666/py3rcon.
|
|||
The dayzserver script manages what's necessary to configure and run it:
|
||||
|
||||
```
|
||||
docker compose exec main dayzserver rcon
|
||||
docker compose exec server dayzserver rcon
|
||||
```
|
||||
|
||||
To reset the RCON password in the Battle Eye configuration file, simply delete it, and a random one will be generated
|
||||
on the next server startup:
|
||||
|
||||
```
|
||||
docker compose run --rm main rm serverfiles/battleye/baserver_x64_active*
|
||||
docker compose run --rm server rm serverfiles/battleye/baserver_x64_active*
|
||||
```
|
||||
|
||||
### Update the DayZ server files
|
||||
|
@ -136,7 +121,7 @@ docker compose down
|
|||
Then run the command:
|
||||
|
||||
```
|
||||
docker compose run --rm main dayzserver update
|
||||
docker compose run --rm web dayzserver update
|
||||
```
|
||||
|
||||
Don't forget to [bring it back up](#run).
|
||||
|
@ -144,13 +129,13 @@ Don't forget to [bring it back up](#run).
|
|||
### Stop the server
|
||||
|
||||
```
|
||||
docker compose exec main dayzserver stop
|
||||
docker compose exec server dayzserver stop
|
||||
```
|
||||
|
||||
The server doesn't always exit when stopping it (SIGINT). When this happens, it's necessary to force stop it (SIGKILL):
|
||||
|
||||
```
|
||||
docker compose exec main dayzserver force
|
||||
docker compose exec server dayzserver force
|
||||
```
|
||||
|
||||
When the server exits cleanly, i.e. exit code 0, the container also stops. Otherwise, a crash is presumed, and the server will be restarted.
|
||||
|
@ -163,8 +148,8 @@ required. This is not a clean exit, and will cause the server to restart. Manual
|
|||
Interactive interface for managing mods.
|
||||
|
||||
```
|
||||
docker compose exec main dayzserver activate id | add id1 | deactivate id | list | modupdate | remove id
|
||||
docker compose exec main dayzserver a id | add id1 | d id | l | m | r id
|
||||
docker compose exec server dayzserver activate id | add id1 | deactivate id | list | modupdate | remove id
|
||||
docker compose exec server dayzserver a id | add id1 | d id | l | m | r id
|
||||
```
|
||||
|
||||
Look for mods in the [DayZ Workshop](https://steamcommunity.com/app/221100/workshop/). Browse to one. In its URL will be
|
||||
|
@ -172,7 +157,7 @@ an `id` parameter. Here is the URL to SimpleAutoRun: https://steamcommunity.com/
|
|||
add it:
|
||||
|
||||
```
|
||||
docker compose exec main dayzserver add 2264162971
|
||||
docker compose exec web dayzserver add 2264162971
|
||||
```
|
||||
|
||||
Adding and removing mods will add and remove their names from the `-mod=` parameter.
|
||||
|
@ -189,12 +174,12 @@ All the server files persist in a docker volume that represents the container's
|
|||
the running container:
|
||||
|
||||
```
|
||||
docker compose exec main bash
|
||||
docker compose exec web bash
|
||||
```
|
||||
|
||||
Or open a shell into a new container if the docker stack is not up:
|
||||
```
|
||||
docker compose run --rm main bash
|
||||
docker compose run --rm web bash
|
||||
```
|
||||
|
||||
All the files used by the server are in a docker volume. Any change made will be reflected upon the next container startup.
|
||||
|
|
|
@ -43,15 +43,10 @@ SERVER_INSTALL_FILE="${SERVER_FILES}/DayZServer"
|
|||
STEAM_LOGIN="${HOME}/steamlogin"
|
||||
STEAMCMD=steamcmd
|
||||
|
||||
# Workshop files (mods)
|
||||
WORKSHOP_DIR="${SERVER_FILES}/steamapps/workshop/content/${release_client_appid}"
|
||||
|
||||
# Other stuff
|
||||
YES="${green}yes${default}"
|
||||
NO="${red}no${default}"
|
||||
|
||||
# Functions
|
||||
|
||||
# Convenience function
|
||||
prompt_yn(){
|
||||
echo -n "${1} (y|N) " >&2
|
||||
|
@ -65,71 +60,3 @@ prompt_yn(){
|
|||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_mod_install(){
|
||||
# See if this mod id exists in files/mods, and offer to install other server side files if an install.sh is found
|
||||
if [ -f ${FILES}/mods/${1}/${2}.sh ]
|
||||
then
|
||||
echo "An ${2}.sh was found for mod id ${1}. Running..."
|
||||
${FILES}/mods/${1}/${2}.sh
|
||||
fi
|
||||
# A generic map install script. Presumes a git repo as the source
|
||||
if [ -f ${FILES}/mods/${1}/install.env ]
|
||||
then
|
||||
echo "An ${2}.env was found for mod id ${1}. Performing ${2}..."
|
||||
source ${FILES}/mods/${1}/install.env
|
||||
${FILES}/mods/install.sh ${1} ${2}
|
||||
fi
|
||||
}
|
||||
|
||||
get_mod_id_by_index2(){
|
||||
# If we were passed a valid mod id, just return it
|
||||
if [ -d "${WORKSHOP_DIR}/${1}" ]
|
||||
then
|
||||
echo -n ${1}
|
||||
return
|
||||
fi
|
||||
X=1
|
||||
# Loop over mods
|
||||
for dir in $(ls -tr ${WORKSHOP_DIR})
|
||||
do
|
||||
ID=${dir}
|
||||
if [[ ${X} = ${1} ]]
|
||||
then
|
||||
echo -n ${ID}
|
||||
return
|
||||
fi
|
||||
X=$((X+1))
|
||||
done
|
||||
}
|
||||
|
||||
# Get mod name by ID or index
|
||||
get_mod_name(){
|
||||
ID=$(get_mod_id_by_index2 ${1})
|
||||
if ! [ -d "${WORKSHOP_DIR}/${ID}" ]
|
||||
then
|
||||
echo "Mod ID ${1} doesn't exist" >&2
|
||||
exit 1
|
||||
fi
|
||||
NAME=$(grep name ${WORKSHOP_DIR}/${ID}/meta.cpp | cut -d '"' -f2 | sed -r 's/\s+//g')
|
||||
echo -n ${NAME}
|
||||
}
|
||||
|
||||
# List mods
|
||||
list(){
|
||||
X=1
|
||||
C="${green}"
|
||||
spaces=" "
|
||||
echo "Installed mods:"
|
||||
echo -e " ID Name URL Size"
|
||||
echo "------------------------------------------------------------------------------------------------------------------------"
|
||||
for dir in $(ls -tr ${WORKSHOP_DIR})
|
||||
do
|
||||
ID=${dir}
|
||||
NAME=$(grep name "${WORKSHOP_DIR}/${dir}/meta.cpp" | cut -d '"' -f2 | sed -r 's/\s+//g')
|
||||
SIZE=$(du -sh "${WORKSHOP_DIR}/${dir}" | awk '{print $1}')
|
||||
printf "${C}%.3d %s %.30s %s https://steamcommunity.com/sharedfiles/filedetails/?id=%s %s${default}\n" ${X} ${ID} "${NAME}" "${spaces:${#NAME}+1}" ${ID} ${SIZE}
|
||||
X=$((X+1))
|
||||
done
|
||||
echo
|
||||
}
|
||||
|
|
|
@ -7,9 +7,8 @@ export PS1="${debian_chroot:+($debian_chroot)}\u@dz-server:\w\$ "
|
|||
EOF
|
||||
|
||||
# Uncomment the line below to run things manually in the container, then run:
|
||||
# docker compose exec main bash
|
||||
#tail -f /dev/null
|
||||
#exit 0
|
||||
# docker compose exec main bash, and from within the container: dz start (or do whatever)
|
||||
tail -f /dev/null
|
||||
|
||||
# Otherwise, start the server normally
|
||||
dz start
|
||||
#dz start
|
||||
|
|
72
web/bin/dz
72
web/bin/dz
|
@ -30,6 +30,41 @@ ${default}"
|
|||
exit 1
|
||||
}
|
||||
|
||||
get_mod_id(){
|
||||
# If we were passed a valid mod id, just return it
|
||||
if [ -d "${WORKSHOP_DIR}/${1}" ]
|
||||
then
|
||||
echo -n ${1}
|
||||
return
|
||||
fi
|
||||
X=1
|
||||
# Loop over mods
|
||||
for dir in $(ls -tr ${WORKSHOP_DIR})
|
||||
do
|
||||
ID=${dir}
|
||||
if [[ ${X} = ${1} ]]
|
||||
then
|
||||
echo -n ${ID}
|
||||
return
|
||||
fi
|
||||
X=$((X+1))
|
||||
done
|
||||
}
|
||||
|
||||
# Get mod name by ID or index
|
||||
get_mod_name(){
|
||||
ID=$(get_mod_id ${1})
|
||||
echo "ID: ${ID}" >&2
|
||||
exit 0
|
||||
if ! [ -d "${WORKSHOP_DIR}/${ID}" ]
|
||||
then
|
||||
echo "Mod ID ${1} doesn't exist" >&2
|
||||
exit 1
|
||||
fi
|
||||
NAME=$(grep name ${WORKSHOP_DIR}/${ID}/meta.cpp | cut -d '"' -f2 | sed -r 's/\s+//g')
|
||||
echo -n ${NAME}
|
||||
}
|
||||
|
||||
# "Manage" XML files.
|
||||
xml(){
|
||||
/files/mods/xml.sh ${1}
|
||||
|
@ -246,7 +281,7 @@ get_mods(){
|
|||
mod_command_line=""
|
||||
for link in $(ls -tdr ${SERVER_FILES}/@* 2> /dev/null)
|
||||
do
|
||||
ID=$(readlink ${link} | cut -d/ -f7)
|
||||
ID=$(readlink ${link} | awk -F/ '{print $NF}')
|
||||
MODNAME=$(get_mod_name ${ID})
|
||||
workshoplist+=" +workshop_download_item "${release_client_appid}" "${ID}
|
||||
mod_command_line+="@${MODNAME};"
|
||||
|
@ -313,6 +348,41 @@ Mods: "
|
|||
echo -e "${MODS}"
|
||||
}
|
||||
|
||||
check_mod_install(){
|
||||
# See if this mod id exists in files/mods, and offer to install other server side files if an install.sh is found
|
||||
if [ -f ${FILES}/mods/${1}/${2}.sh ]
|
||||
then
|
||||
echo "An ${2}.sh was found for mod id ${1}. Running..."
|
||||
${FILES}/mods/${1}/${2}.sh
|
||||
fi
|
||||
# A generic map install script. Presumes a git repo as the source
|
||||
if [ -f ${FILES}/mods/${1}/install.env ]
|
||||
then
|
||||
echo "An ${2}.env was found for mod id ${1}. Performing ${2}..."
|
||||
source ${FILES}/mods/${1}/install.env
|
||||
${FILES}/mods/install.sh ${1} ${2}
|
||||
fi
|
||||
}
|
||||
|
||||
# List mods
|
||||
list(){
|
||||
X=1
|
||||
C="${green}"
|
||||
spaces=" "
|
||||
echo "Installed mods:"
|
||||
echo -e " ID Name URL Size"
|
||||
echo "------------------------------------------------------------------------------------------------------------------------"
|
||||
for dir in $(ls -tr ${WORKSHOP_DIR})
|
||||
do
|
||||
ID=${dir}
|
||||
NAME=$(grep name "${WORKSHOP_DIR}/${dir}/meta.cpp" | cut -d '"' -f2 | sed -r 's/\s+//g')
|
||||
SIZE=$(du -sh "${WORKSHOP_DIR}/${dir}" | awk '{print $1}')
|
||||
printf "${C}%.3d %s %.30s %s https://steamcommunity.com/sharedfiles/filedetails/?id=%s %s${default}\n" ${X} ${ID} "${NAME}" "${spaces:${#NAME}+1}" ${ID} ${SIZE}
|
||||
X=$((X+1))
|
||||
done
|
||||
echo
|
||||
}
|
||||
|
||||
# Capture the first argument and shift it off so we can pass $@ to every function
|
||||
C=${1}
|
||||
shift || {
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
body {
|
||||
padding: 10px;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 5px;
|
||||
margin: 10px;
|
||||
|
@ -12,9 +7,6 @@ th, td {
|
|||
padding-right: 10px
|
||||
}
|
||||
|
||||
.result {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: cyan;
|
||||
|
@ -24,3 +16,7 @@ th, td {
|
|||
cursor: pointer;
|
||||
text-underline: blue;
|
||||
}
|
||||
|
||||
.simulink:hover {
|
||||
background-color: cyan;
|
||||
}
|
||||
|
|
|
@ -4,19 +4,20 @@
|
|||
<meta charset="UTF-8">
|
||||
<title>DayZ Docker Server</title>
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.png">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.3.0/font/bootstrap-icons.css"> <link rel="stylesheet" href="/index.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
||||
<!-- Bootstrap -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
|
||||
<!-- Bootstrap Icons -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.3.0/font/bootstrap-icons.css">
|
||||
<!-- Our CSS -->
|
||||
<link rel="stylesheet" href="/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module">
|
||||
import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
|
||||
import app from '/index.js'
|
||||
// eslint-disable-next-line no-undef
|
||||
Vue.createApp(app).mount('#app')
|
||||
createApp(app).mount('#app')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,49 +1,65 @@
|
|||
const template = `
|
||||
<div class="container-fluid bg-light">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="text-center">
|
||||
<h1>DayZ Docker Server</h1>
|
||||
<div
|
||||
class="modal"
|
||||
id="staticBackdrop"
|
||||
data-bs-backdrop="static"
|
||||
data-bs-keyboard="false"
|
||||
tabindex="-1"
|
||||
aria-labelledby="staticBackdropLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="staticBackdropLabel">Error</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<div class="text-center">
|
||||
<button
|
||||
@click="install"
|
||||
:class="'btn ' + (installed ? 'btn-danger' : 'btn-success')"
|
||||
>
|
||||
Install Server Files
|
||||
</button>
|
||||
<button @click="updatebase" class="btn btn-warning">Update Base</button>
|
||||
<button @click="updatemods" class="btn btn-warning">Update Mods</button>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<button @click="server" class="btn btn-primary">Server</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-control-lg">
|
||||
<form @submit="handleSubmit">
|
||||
<input name="search" placeholder="Search mods..." autofocus>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{ fetchError }}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid min-vh-100 d-flex flex-column bg-light">
|
||||
<div class="row">
|
||||
<div class="col-3 text-center">
|
||||
<h1>DayZ Docker Server</h1>
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<button
|
||||
@click="installbase"
|
||||
:class="'btn ' + (installed ? 'btn-danger' : 'btn-success')"
|
||||
>
|
||||
Install Server Files
|
||||
</button>
|
||||
<button @click="updatebase" class="btn btn-success">Update Server Files</button>
|
||||
<button @click="updatemods" class="btn btn-success">Update Mods</button>
|
||||
<button @click="servers" class="btn btn-primary">Servers</button>
|
||||
<button @click="listmods" class="btn btn-primary">Mods</button>
|
||||
</div>
|
||||
<div class="col form-control-lg text-center">
|
||||
<form @submit="handleSubmit">
|
||||
<input name="search" placeholder="Search mods..." autofocus>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div>
|
||||
<div class="justify-right">
|
||||
Server files installed:
|
||||
<span class="bi bi-check h2 text-success" v-if="installed"></span>
|
||||
<span class="bi bi-x h2 danger text-danger" v-else></span>
|
||||
</div>
|
||||
<div v-if="version != ''">
|
||||
Version: <span class="text-success font-weight-bold">{{ version }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="fetchError != ''"
|
||||
class="text-center alert alert-danger"
|
||||
>
|
||||
{{ fetchError }}
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
Server files installed:
|
||||
<span class="bi bi-check h2 text-success" v-if="installed"></span>
|
||||
<span class="bi bi-x h2 danger text-danger" v-else></span>
|
||||
</div>
|
||||
<div v-if="version != ''">
|
||||
Version: <span class="text-success font-weight-bold">{{ version }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row flex-grow-1">
|
||||
<div class="col-md-3 border">
|
||||
<div>
|
||||
<h2 class="text-center">Mods</h2>
|
||||
<h4 class="text-center">Installed Mods</h4>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Steam Link</th>
|
||||
|
@ -68,40 +84,39 @@ const template = `
|
|||
</template>
|
||||
</table>
|
||||
</div>
|
||||
<div v-if="modInfo != ''">
|
||||
</div>
|
||||
<div class="col-md-9 border">
|
||||
<div class="d-flex" v-if="modInfo != ''">
|
||||
<div>
|
||||
<h3>{{ modInfo.name }}</h3>
|
||||
<div class="d-flex">
|
||||
<div>
|
||||
<div>
|
||||
ID: {{ modInfo.id }}
|
||||
</div>
|
||||
<div>
|
||||
Size: {{ modInfo.size.toLocaleString("en-US") }}
|
||||
</div>
|
||||
<div v-if="modInfo.customXML.length > 0">
|
||||
Custom XML files:
|
||||
<ul>
|
||||
<li v-for="info in modInfo.customXML">
|
||||
<a
|
||||
:class="'simulink xmlfile ' + info.name"
|
||||
@click="getXMLInfo(modInfo.id,info.name)"
|
||||
>
|
||||
{{ info.name }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div v-if="XMLInfo != ''">
|
||||
<textarea cols="120" rows="15" v-if="this.XMLInfo != ''">{{ this.XMLInfo }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>{{ modInfo.name }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
ID: {{ modInfo.id }}
|
||||
</div>
|
||||
<div>
|
||||
Size: {{ modInfo.size.toLocaleString("en-US") }}
|
||||
</div>
|
||||
<div v-if="modInfo.customXML.length > 0">
|
||||
Custom XML files:
|
||||
<ul>
|
||||
<li v-for="info in modInfo.customXML">
|
||||
<a
|
||||
:class="'simulink xmlfile ' + info.name"
|
||||
@click="getXMLInfo(modInfo.id,info.name)"
|
||||
>
|
||||
{{ info.name }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-1"></div>
|
||||
<div>
|
||||
<xmltree v-if="XMLInfo != ''" :xmlData="XMLInfo" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="searchResults != ''">
|
||||
<div v-if="searchResults != ''" class="d-flex">
|
||||
<table>
|
||||
<tr>
|
||||
<th>Steam Link</th>
|
||||
|
@ -132,12 +147,30 @@ const template = `
|
|||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
import xmltree from "/xmltree.js"
|
||||
|
||||
const fetcher = (args) => {
|
||||
fetch(args.url)
|
||||
.then(response => (
|
||||
args.type === "json" ? response.json() : response.text()
|
||||
))
|
||||
.then(response => args.callback(response))
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
this.fetchError = error.message
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'DazDockerServer',
|
||||
template: template,
|
||||
components: {
|
||||
xmltree
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fetchError: "",
|
||||
|
@ -153,17 +186,15 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
getModInfo(modId) {
|
||||
fetch('/mod/' + modId)
|
||||
.then(response => response.json())
|
||||
.then(response => {
|
||||
fetcher ({
|
||||
url: '/mod/' + modId,
|
||||
type: "json",
|
||||
callback: (response) => {
|
||||
this.modInfo = response
|
||||
this.XMLInfo = ""
|
||||
this.searchResults = ""
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
this.fetchError = error.message
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
getXMLInfo(modId, file) {
|
||||
for (const e of document.getElementsByClassName("selected")) e.classList.remove("selected")
|
||||
|
@ -199,6 +230,8 @@ export default {
|
|||
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl)
|
||||
})
|
||||
// Enable all alerts
|
||||
$('.alert').alert()
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
|
@ -234,6 +267,21 @@ export default {
|
|||
n = n/1024
|
||||
}
|
||||
return(n.toFixed(n < 10 && l > 0 ? 1 : 0) + ' ' + units[l])
|
||||
},
|
||||
installbase() {
|
||||
console.log("Install base files")
|
||||
},
|
||||
servers() {
|
||||
console.log("List servers")
|
||||
},
|
||||
listmods() {
|
||||
console.log("List mods")
|
||||
},
|
||||
updatebase() {
|
||||
console.log("Update base files")
|
||||
},
|
||||
updatemods() {
|
||||
console.log("Update mod files")
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
@ -246,6 +294,9 @@ export default {
|
|||
this.mods = response.mods
|
||||
if(response.error) {
|
||||
this.fetchError = response.error
|
||||
// Since it's a modal, we have to manually show it...?
|
||||
const modal = new bootstrap.Modal(document.getElementById('staticBackdrop'))
|
||||
modal.show()
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
103
web/root/xmltree.js
Normal file
103
web/root/xmltree.js
Normal file
|
@ -0,0 +1,103 @@
|
|||
const template = `
|
||||
<div
|
||||
v-if="elem.nodeType === 1 && isText"
|
||||
:style="'padding-left: ' + (depth * 10) + 'px'"
|
||||
@click="collapse"
|
||||
>
|
||||
<span class="xml-tree-tag"><{{elem.nodeName}}</span>
|
||||
<span v-if="elem.hasAttributes()" v-for="attribute in elem.attributes">
|
||||
<span class="xml-tree-attr"> {{attribute.name}}</span>
|
||||
<span>=</span>
|
||||
<span class="xml-tree-attr">"{{attribute.value}}"</span>
|
||||
</span>
|
||||
<span class="xml-tree-tag">></span>
|
||||
<span>{{this.children[0].data.trim()}}</span>
|
||||
<span class="xml-tree-tag"></{{elem.nodeName}}></span>
|
||||
</div>
|
||||
<div v-else :style="'padding-left: ' + (depth * 10) + 'px'">
|
||||
<span v-if="elem.nodeType === 1" class="d-flex">
|
||||
<span
|
||||
v-if="elem.children.length > 0"
|
||||
class="bi-dash simulink text-center"
|
||||
@click="collapse"
|
||||
/>
|
||||
<span v-else></span>
|
||||
<span class="xml-tree-tag"><{{elem.nodeName}}</span>
|
||||
<span v-if="elem.hasAttributes()" v-for="attribute in elem.attributes">
|
||||
<span class="xml-tree-attr"> {{attribute.name}}</span>
|
||||
<span>=</span>
|
||||
<span class="xml-tree-attr">"{{attribute.value}}"</span>
|
||||
</span>
|
||||
<span v-if="elem.children.length === 0" class="xml-tree-tag"> /></span>
|
||||
<span v-else class="xml-tree-tag">></span>
|
||||
</span>
|
||||
<span v-if="elem.nodeType === 3">{{elem.data.trim()}}</span>
|
||||
<div v-for="child in children">
|
||||
<xmltree v-if="child.nodeType !== 8" :element="child" :d="depth" />
|
||||
</div>
|
||||
<span
|
||||
v-if="elem.nodeType === 1 && elem.children.length > 0"
|
||||
style="padding-left: -10px"
|
||||
>
|
||||
<span style="padding-left: 20px" class="xml-tree-tag"></{{elem.nodeName}}></span>
|
||||
</span>
|
||||
</div>
|
||||
`
|
||||
|
||||
export default {
|
||||
name: "xmltree",
|
||||
props: {
|
||||
d: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
element: {
|
||||
type: [Element, Text],
|
||||
default: undefined
|
||||
},
|
||||
xmlData: String
|
||||
},
|
||||
template: template,
|
||||
data() {
|
||||
return {
|
||||
depth: 1
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
collapse() {
|
||||
this.children.forEach(x => x.classList?.add("d-none"))
|
||||
},
|
||||
log(message) {
|
||||
console.log(message)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
elem() {
|
||||
this.depth = parseInt(this.d) + 1
|
||||
if (this.element) {
|
||||
return this.element
|
||||
} else {
|
||||
const parser = new DOMParser()
|
||||
const xmlDoc = parser.parseFromString(this.xmlData, "text/xml")
|
||||
return xmlDoc.documentElement
|
||||
}
|
||||
},
|
||||
children() {
|
||||
let children = []
|
||||
let node = this.elem.firstChild
|
||||
while (node) {
|
||||
children.push(node)
|
||||
node = node.nextSibling
|
||||
}
|
||||
return children
|
||||
},
|
||||
isText() {
|
||||
if (this.children.length === 1) {
|
||||
if (this.children[0].nodeType === 3) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
26
web/web.js
26
web/web.js
|
@ -10,6 +10,7 @@ import express from 'express'
|
|||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import https from 'https'
|
||||
import { spawn } from 'child_process'
|
||||
|
||||
const app = express()
|
||||
|
||||
|
@ -154,6 +155,28 @@ const getMods = () => {
|
|||
return mods
|
||||
}
|
||||
|
||||
const login = () => {
|
||||
const args = "+force_install_dir " + serverFiles + " +login '" + config.steamLogin + "' +quit"
|
||||
steamcmd(args)
|
||||
}
|
||||
|
||||
const steamcmd = (args) => {
|
||||
const proc = spawn('steamcmd ' + args)
|
||||
proc.stdout.on('data', (data) => {
|
||||
res.write(data)
|
||||
})
|
||||
proc.stderr.on('data', (data) => {
|
||||
res.write(data)
|
||||
})
|
||||
proc.on('error', (error) => {
|
||||
res.write(error)
|
||||
})
|
||||
proc.on('close', (error) => {
|
||||
if(error) res.write(error)
|
||||
res.end()
|
||||
})
|
||||
}
|
||||
|
||||
app.use(express.static('root'))
|
||||
|
||||
// Get mod metadata by ID
|
||||
|
@ -211,6 +234,7 @@ app.get(('/remove/:modId'), (req, res) => {
|
|||
|
||||
// Update base files
|
||||
app.get('/updatebase', (req, res) => {
|
||||
login()
|
||||
res.send("Base files were updates")
|
||||
})
|
||||
|
||||
|
@ -229,10 +253,12 @@ app.get('/status', (req, res) => {
|
|||
const mods = getMods()
|
||||
const version = getVersion(installed)
|
||||
const ret = {
|
||||
"appid": server_appid,
|
||||
"installed": installed,
|
||||
"version": version,
|
||||
"mods": mods
|
||||
}
|
||||
// ret.error = "This is a test error from the back end"
|
||||
res.send(ret)
|
||||
})
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue