mirror of
https://ceregatti.org/git/daniel/dayzdockerserver.git
synced 2025-05-06 14:21:18 +00:00
First working version with vite-express integration.
Refactor the main page to show the Steam login when none is detected. We can't do anything without it at first. Later, we can detect if the server is installed and allow operations. Or not. Remove server container. This will be created and maintained by the web container going forward. Use node 18, as vite requires it. Use a single package.json for everything. This way it can be installed at the root of the container and not show up in the bind mount. Refactor store to include actions. We can just define them and call them, instead of using fetch directly everywhere. WIP. Begin to implement some of the backend methods, like the steam login. It works! Remove the old code.
This commit is contained in:
parent
28fba94599
commit
85e59ae8c6
30 changed files with 2495 additions and 2624 deletions
277
README.md
277
README.md
|
@ -1,27 +1,19 @@
|
|||
# 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 orchestration tool in a [Docker](https://docs.docker.com/) container.
|
||||
|
||||
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.
|
||||
## Features
|
||||
|
||||
This project started when the Linux DayZ server was released for DayZ experimental version 1.14 on 09/02/2021. As of 02/20/2024, there is now an official Linux DayZ server, but...
|
||||
* [SteamCMD](https://developer.valvesoftware.com/wiki/SteamCMD) integration for installing the DayZ server and mods.
|
||||
* A web UI for logging into Steam, managing server files and mods, and provisioning and running servers.
|
||||
* Seamless integrations for many popular mods. Many require XML/JSON modifications or additions to the game files to work properly. See [files/mods/README.md](./files/mods/README.md) for a list of supported mods.
|
||||
* Base server files are restored to their upstream state before every server start and all mod integrations are re-applied at that time. This allows for seamless upgrades when the server files are updated (as long as XML schema doesn't change, which is rare).
|
||||
* Everything runs in Docker containers. Docker volumes are used to persist data. The web tool is the main container, which orchestrates server containers, however many your machine can handle.
|
||||
* Shared docker volumes. All servers share the server files and mod volumes. Avoids duplication of many gigs of files. Updates are faster as there is only one copy.
|
||||
|
||||
## Caveats
|
||||
## Build and Run
|
||||
|
||||
* Some mods are known to crash the server on startup:
|
||||
* [DayZ Expansion AI](https://steamcommunity.com/sharedfiles/filedetails/?id=2792982069)
|
||||
* [Red Falcon Flight System Heliz](https://steamcommunity.com/workshop/filedetails/?id=2692979668) - Bug report [here](https://feedback.bistudio.com/T176564)
|
||||
* Some mods work, but have bugs:
|
||||
* [DayZ Expansion Groups](https://steamcommunity.com/sharedfiles/filedetails/?id=2792983364)
|
||||
* The save file becomes corrupted and when the server restarts so the changes do not persist.
|
||||
* There are other bugs:
|
||||
* [Server doesn't stop with SIGTERM](https://feedback.bistudio.com/T170721)
|
||||
|
||||
This project is a work in progress: See the [roadmap](ROADMAP.md).
|
||||
|
||||
## 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/) and [git](https://git-scm.com/) are properly installed. Make sure you're running these commands as your user, in your home directory, not as root.
|
||||
|
||||
Clone the repo, and change into the newly created directory:
|
||||
|
||||
|
@ -30,245 +22,48 @@ git clone https://ceregatti.org/git/daniel/dayzdockerserver.git
|
|||
cd dayzdockerserver
|
||||
```
|
||||
|
||||
Create a `.env` file for the web container that contains your user id. Usually the `${UID}` shell variable has this:
|
||||
```shell
|
||||
Build the Docker image and bring the stack up in the background:
|
||||
|
||||
```shell
|
||||
echo "export USER_ID=${UID}" | tee .env
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
Repeat the above for server1, which uses .env1 (and so on):
|
||||
```shell
|
||||
echo "export USER_ID=${UID}" | tee .env1
|
||||
```
|
||||
|
||||
But each server must also set its own unique ports and id:
|
||||
```shell
|
||||
echo "export SERVER_ID=1" | tee -a .env1
|
||||
echo "export SERVER_PORT=2302" | tee -a .env1
|
||||
echo "export RCON_PORT=2303" | tee -a .env1
|
||||
echo "export STEAM_PORT=27016" | tee -a .env1
|
||||
```
|
||||
|
||||
Repeat the above for each server you want to run, making sure the ports are unique across all servers:
|
||||
```shell
|
||||
echo "export USER_ID=${UID}" | tee .env2
|
||||
echo "export SERVER_ID=2" | tee -a .env2
|
||||
echo "export SERVER_PORT=2312" | tee -a .env2
|
||||
echo "export RCON_PORT=2313" | tee -a .env2
|
||||
echo "export STEAM_PORT=27116" | tee -a .env2
|
||||
|
||||
Tail the log to see when the web UI is ready, and get the URL and password:
|
||||
|
||||
```shell
|
||||
Build the Docker images:
|
||||
|
||||
```shell
|
||||
docker compose build
|
||||
docker compose logs -f web
|
||||
```
|
||||
|
||||
### Steam Integration
|
||||
Hit control c to exit the log. Go to the URL shown in the log.
|
||||
|
||||
[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:
|
||||
## Web UI
|
||||
|
||||
```shell
|
||||
docker compose run --rm web dz login
|
||||
```
|
||||
Once at the web UI, you will be prompted to set a username and password for the web tool. Once done, a form will be presented to log into Steam. Downloading DayZ server files and mods using `steamcmd` requires a Steam account with DayZ in the library. The values will be first used by the `steamcmd` command line to perform a one-time login to Steam, and when successful, create a session that requires only that the username be saved locally. This supports Steam Guard codes. Neither the password nor the code will be stored. Subsequent calls to `steamcmd` by the web container will not require the credentials again until the session expires, which may be many months.
|
||||
|
||||
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.
|
||||
After a successful login to Steam, the server files will automatically start downloading. The web UI will show the main page, which will list the progress of the download. The server files are about 3GB.
|
||||
|
||||
The credentials will be managed by [SteamCMD](https://developer.valvesoftware.com/wiki/SteamCMD). This will store a session token in the `homedir` docker volume. All subsequent SteamCMD commands will use this. so this process does not need to be repeated unless the session expires or the docker volume is deleted.
|
||||
### Main Page
|
||||
|
||||
To manage the login credentials, simply run the above command again. See [Manage](#manage).
|
||||
### Installing workshop mods
|
||||
|
||||
## Install
|
||||
### Provisioning servers
|
||||
|
||||
The base server files must be installed before the server can be run:
|
||||
```shell
|
||||
docker compose run --rm web dz install
|
||||
```
|
||||
## Linux Server Caveats
|
||||
|
||||
This will download about 2.9G of files.
|
||||
|
||||
## Run
|
||||
|
||||
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/) if you want, but most of the default values are fine. At the very least, change the server name:
|
||||
|
||||
```
|
||||
hostname = "Something other than Server Name"; // Server name
|
||||
```
|
||||
|
||||
Install the server config file:
|
||||
|
||||
```shell
|
||||
docker compose run --rm server1 dz c
|
||||
```
|
||||
|
||||
The maintenance of the config file is a work in progress. The goal is to create a facility for merging changes into the config file and maintain a paper trail of changes.
|
||||
|
||||
Launch the stack into the background:
|
||||
```shell
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
There will be nothing in mpmissions when a server container starts for the first time. A pristine copy of `dayzOffline.chernarusplus` will be copied from the `mpmission` volume to the server container. This will be the default map. To install other maps, see [Maps](#maps).
|
||||
|
||||
To see the server log:
|
||||
|
||||
```shell
|
||||
docker compose logs -f server1
|
||||
```
|
||||
## Stopping the server
|
||||
|
||||
To stop the DayZ server:
|
||||
```shell
|
||||
docker compose exec server1 dz stop
|
||||
```
|
||||
|
||||
If it exits cleanly, the container will also stop. Otherwise a crash is presumed and the server will restart. Ideally, the server would always exit cleanly, but... it's DayZ.
|
||||
|
||||
To stop the containers:
|
||||
```shell
|
||||
docker compose stop
|
||||
```
|
||||
|
||||
To bring the entire stack down:
|
||||
```shell
|
||||
docker compose down
|
||||
```
|
||||
|
||||
## Manage
|
||||
|
||||
### Maps
|
||||
|
||||
Installing another map requires installing its mod and mpmissions files. Some maps maintain github repositories or public web sites for their mpmissions, while others do not. This project aims to support DayZ maps whose mpmissions are easily accessible "Out of the box" by maintaining configuration files for them.
|
||||
|
||||
The following management commands presume the server has been brought [up](#run).
|
||||
|
||||
### RCON
|
||||
|
||||
A terminal-based RCON client is included: https://github.com/indepth666/py3rcon.
|
||||
The dz script manages what's necessary to configure and run it:
|
||||
|
||||
```shell
|
||||
docker compose exec server1 dz 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:
|
||||
|
||||
```shell
|
||||
docker compose run --rm server1 rm serverfiles/battleye/baserver_x64_active*
|
||||
```
|
||||
|
||||
Don't expect much from this RCON at this time.
|
||||
|
||||
### Update the DayZ server files
|
||||
|
||||
It's probably not a good idea to update the server files while a server is running. Bring everything down first:
|
||||
|
||||
```shell
|
||||
docker compose down
|
||||
```
|
||||
|
||||
Then run the command:
|
||||
|
||||
```shell
|
||||
docker compose run --rm web dz update
|
||||
```
|
||||
|
||||
This will update the server base files as well as all installed mods.
|
||||
|
||||
Don't forget to [bring it back up](#run).
|
||||
|
||||
### Stop the DayZ server
|
||||
|
||||
To stop the server:
|
||||
```shell
|
||||
docker compose exec server1 dz stop
|
||||
```
|
||||
|
||||
The above sends the SIGINT signal to the server process. The server sometimes fails to stop with this signal. It may be necessary to force stop it with the SIGKILL:
|
||||
|
||||
```shell
|
||||
docker compose exec server1 dz 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 automatically restarted.
|
||||
|
||||
To bring the entire stack down:
|
||||
```shell
|
||||
docker compose down
|
||||
```
|
||||
|
||||
### Workshop - Add / List / Remove / Update mods
|
||||
|
||||
Interactive interface for managing mods.
|
||||
|
||||
```
|
||||
docker compose exec server1 dz activate id | add id1 | deactivate id | list | modupdate | remove id
|
||||
docker compose exec server1 dz 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
|
||||
an `id` parameter. Here is the URL to SimpleAutoRun: https://steamcommunity.com/sharedfiles/filedetails/?id=2264162971. To
|
||||
add it:
|
||||
|
||||
```
|
||||
docker compose exec web dz add 2264162971
|
||||
```
|
||||
|
||||
Adding and removing mods will add and remove their names from the `-mod=` parameter.
|
||||
|
||||
Optionally, to avoid re-downloading large mods, the `activate` and `deactivate` workshop commands will
|
||||
simply disable the mod but keep its files. Keep in mind that mod updates will also update deactivated
|
||||
mods.
|
||||
|
||||
The above is a bad example, as SimpleAutorun depends on Community Framework, which must also be installed, as well as made to load first.
|
||||
|
||||
### Looking under the hood
|
||||
|
||||
All the server files persist in a docker volume that represents the container's unprivileged user's home directory. Open a bash shell in
|
||||
the running container:
|
||||
|
||||
```
|
||||
docker compose exec web bash
|
||||
```
|
||||
|
||||
Or open a shell into a new container if the docker stack is not up:
|
||||
```
|
||||
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.
|
||||
|
||||
Use this shell cautiously.
|
||||
|
||||
# Development mode
|
||||
|
||||
Add the following to the `.env` file:
|
||||
|
||||
```shell
|
||||
export DEVELOPMENT=1
|
||||
```
|
||||
|
||||
Bring the stack down then back up. Now, instead of the server starting when the server container comes up it will simply block, keeping the container up and accessible.
|
||||
|
||||
This allows access to the server container using exec. You can then start and stop the server manually, using `dz`:
|
||||
|
||||
```shell
|
||||
# Go into the server container
|
||||
docker compose exec server1 bash
|
||||
# See what the server status is
|
||||
dz s
|
||||
# Start it
|
||||
dz start
|
||||
```
|
||||
|
||||
To stop the server, hit control c.
|
||||
|
||||
Caveat: Some times the server doesn't stop with control c. If that's the case, control z, exit, then `dz f`. YMMV.
|
||||
* Some mods are known to crash the server on startup or when a player connects:
|
||||
* [DayZ Expansion AI](https://steamcommunity.com/sharedfiles/filedetails/?id=2792982069)
|
||||
* [Survivor Animations](https://steamcommunity.com/sharedfiles/filedetails/?id=2918418331)
|
||||
* [Red Falcon Flight System Heliz](https://steamcommunity.com/workshop/filedetails/?id=2692979668) - Bug report [here](https://feedback.bistudio.com/T176564)
|
||||
* All these mods are like due to the same underlying bug.
|
||||
* Some mods work, but have bugs:
|
||||
* [DayZ Expansion Groups](https://steamcommunity.com/sharedfiles/filedetails/?id=2792983364)
|
||||
* The save file becomes corrupted and when the server restarts so the changes do not persist.
|
||||
* There are other bugs:
|
||||
* [Server doesn't stop with SIGTERM](https://feedback.bistudio.com/T170721)
|
||||
|
||||
## TODO
|
||||
|
||||
* Create web management tool:
|
||||
* It shells out to `dz` (for now) for all the heavy lifting.
|
||||
* Create some way to send messages to players on the server using RCON.
|
||||
* Implement multiple ids for mod commands. (In progress)
|
||||
* Add CF tools
|
||||
* Support for experimental servers.
|
||||
* Rootless Docker
|
|
@ -2,149 +2,34 @@ volumes:
|
|||
# Only in the web container.
|
||||
# For steamcmd files and resource files used by the scripts.
|
||||
homedir_main:
|
||||
|
||||
# Shared by all containers.
|
||||
# Mods.
|
||||
mods:
|
||||
# Where the server files will be installed
|
||||
# Server files
|
||||
serverfiles:
|
||||
# Upstream mission files
|
||||
servermpmissions:
|
||||
# Server-specific volumes
|
||||
# For Steam, for now
|
||||
homedir_server1:
|
||||
homedir_server2:
|
||||
homedir_server3:
|
||||
# Server mission files
|
||||
mpmissions1:
|
||||
mpmissions2:
|
||||
mpmissions3:
|
||||
# Server profile files
|
||||
profiles1:
|
||||
profiles2:
|
||||
profiles3:
|
||||
mpmissions:
|
||||
|
||||
services:
|
||||
|
||||
web:
|
||||
profiles:
|
||||
- main
|
||||
build:
|
||||
context: web
|
||||
args:
|
||||
- USER_ID
|
||||
user: ${USER_ID}
|
||||
hostname: web
|
||||
volumes:
|
||||
- homedir_main:/home/user
|
||||
- serverfiles:/serverfiles
|
||||
- servermpmissions:/serverfiles/mpmissions
|
||||
- mpmissions:/serverfiles/mpmissions
|
||||
- mods:/serverfiles/steamapps/workshop/content
|
||||
- mods:/mods
|
||||
- ./files:/files
|
||||
- ./web:/web
|
||||
ports:
|
||||
- "8001:8001/tcp"
|
||||
- "8000:8000/tcp"
|
||||
restart: always
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
server-image:
|
||||
&server-image
|
||||
profiles:
|
||||
- build
|
||||
build:
|
||||
context: server
|
||||
args:
|
||||
- USER_ID
|
||||
image: server-image
|
||||
pull_policy: never
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
server1:
|
||||
<<: *server-image
|
||||
profiles:
|
||||
- main
|
||||
user: ${USER_ID}
|
||||
volumes:
|
||||
# Common volumes
|
||||
- ./files:/files
|
||||
- mods:/mods
|
||||
- ./server:/server
|
||||
- serverfiles:/serverfiles
|
||||
- servermpmissions:/mpmissions:ro
|
||||
# Server-specific volumes
|
||||
- homedir_server1:/home/user
|
||||
- mpmissions1:/serverfiles/mpmissions
|
||||
- profiles1:/profiles
|
||||
# To have the server show up in the LAN tab of the DayZ launcher,
|
||||
# it must run under host mode.
|
||||
network_mode: host
|
||||
# The above is mutually exclusive with the below. If you don't need
|
||||
# the server to show up on the LAN, comment out the network_mode above
|
||||
# and uncomment the port mappings below.
|
||||
# ports:
|
||||
# # Game port
|
||||
# - 2302:2302/udp
|
||||
# # RCON port
|
||||
# - 2303:2303/udp
|
||||
# # Steam port
|
||||
# - 27016:27016/udp
|
||||
# The server script execs itself when the server exits, unless told not to by `dz stop`
|
||||
restart: no
|
||||
# Allows attaching a debugger from the host
|
||||
# cap_add:
|
||||
# - SYS_PTRACE
|
||||
# Allows core files to be created within the container. These are VERY LARGE! Enable only for debugging!
|
||||
# One must also set the sysctl kernel.core_pattern parameter ON THE HOST to a path that is writable within the container. YMMV
|
||||
# ulimits:
|
||||
# core:
|
||||
# soft: -1
|
||||
# hard: -1
|
||||
env_file:
|
||||
- .env1
|
||||
|
||||
# # Copy and paste this for every other server you want to run, replacing 2 with 3, and so on.
|
||||
# server2: # <-- here
|
||||
# build:
|
||||
# context: server
|
||||
# args:
|
||||
# - USER_ID
|
||||
# user: ${USER_ID}
|
||||
# volumes:
|
||||
# # Common volumes
|
||||
# - ./files:/files
|
||||
# - mods:/mods
|
||||
# - ./server:/server
|
||||
# - serverfiles:/serverfiles
|
||||
# - servermpmissions:/mpmissions:ro
|
||||
# # Server-specific volumes
|
||||
# - homedir_server2:/home/user # <-- here
|
||||
# - mpmissions2:/serverfiles/mpmissions # <-- here
|
||||
# - profiles2:/profiles # <-- here
|
||||
# network_mode: host
|
||||
# restart: no
|
||||
# env_file:
|
||||
# - .env2 # <-- here
|
||||
#
|
||||
# server3: # <-- here
|
||||
# build:
|
||||
# context: server
|
||||
# args:
|
||||
# - USER_ID
|
||||
# user: ${USER_ID}
|
||||
# volumes:
|
||||
# # Common volumes
|
||||
# - ./files:/files
|
||||
# - mods:/mods
|
||||
# - ./server:/server
|
||||
# - serverfiles:/serverfiles
|
||||
# - servermpmissions:/mpmissions:ro
|
||||
# # Server-specific volumes
|
||||
# - homedir_server3:/home/user # <-- here
|
||||
# - mpmissions3:/serverfiles/mpmissions # <-- here
|
||||
# - profiles3:/profiles # <-- here
|
||||
# network_mode: host
|
||||
# restart: no
|
||||
# env_file:
|
||||
# - .env3 # <-- here
|
||||
- .env
|
||||
|
|
|
@ -33,7 +33,7 @@ RUN wget -q https://github.com/WoozyMasta/bercon/releases/download/1.0.0/bercon
|
|||
# Install nodejs
|
||||
RUN mkdir /usr/local/nvm
|
||||
ENV NVM_DIR /usr/local/nvm
|
||||
ENV NODE_VERSION 20.15.1
|
||||
ENV NODE_VERSION 18.20.4
|
||||
|
||||
# Install nvm with node and npm
|
||||
RUN wget -O - https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash \
|
||||
|
@ -109,7 +109,7 @@ ENV LC_ALL=en_US.UTF-8
|
|||
|
||||
# This was installed in the download stage
|
||||
ENV NVM_DIR /usr/local/nvm
|
||||
ENV NODE_VERSION=20.15.1
|
||||
ENV NODE_VERSION=18.20.4
|
||||
|
||||
ENV NODE_PATH=$NVM_DIR/versions/node/v$NODE_VERSION/lib/node_modules
|
||||
ENV PATH=$NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH
|
||||
|
@ -121,6 +121,11 @@ ENV PATH=/usr/games:/files/bin:/web/bin:${PATH}
|
|||
# Shut steamcmd up
|
||||
RUN cd /usr/lib/i386-linux-gnu && ln -s /web/bin/steamservice.so
|
||||
|
||||
# Install node modules
|
||||
COPY package* /
|
||||
|
||||
RUN cd / && npm i
|
||||
|
||||
# Setup a non-privileged user
|
||||
ARG USER_ID
|
||||
|
||||
|
@ -136,5 +141,4 @@ USER user
|
|||
WORKDIR /home/user
|
||||
|
||||
# Run the web server
|
||||
ENTRYPOINT ["entrypoint.sh"]
|
||||
CMD ["start.sh"]
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -eE
|
||||
|
||||
trap '
|
||||
echo "Shutting down..."
|
||||
' SIGINT SIGTERM
|
||||
|
||||
# Set PS1 so we know we're in the container
|
||||
if ! echo .bashrc | grep -q "dz-web"
|
||||
if grep -q "dz-web" .bashrc
|
||||
then
|
||||
echo "Adding PS1 to .bashrc..."
|
||||
cat > .bashrc <<EOF
|
||||
cat >> .bashrc <<EOF
|
||||
alias ls='ls --color'
|
||||
export PS1="${debian_chroot:+($debian_chroot)}\u@dz-web:\w\$ "
|
||||
unset DEVELOPMENT
|
||||
|
@ -19,10 +25,6 @@ then
|
|||
fi
|
||||
|
||||
cd /web
|
||||
npm i
|
||||
export DEBUG='express:*'
|
||||
npx nodemon web.js &
|
||||
|
||||
cd docroot
|
||||
npm i
|
||||
exec npm run dev
|
||||
#export DEBUG=express:*
|
||||
npm run dev &
|
||||
wait $!
|
||||
|
|
1372
web/docroot/package-lock.json
generated
1372
web/docroot/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"name": "docroot",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite --host",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@vueuse/core": "^10.1.2",
|
||||
"bootstrap": "^5.3.0",
|
||||
"bootstrap-icons": "^1.10.5",
|
||||
"pinia": "^2.1.3",
|
||||
"vue": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"vite": "^4.3.5"
|
||||
}
|
||||
}
|
|
@ -1,17 +1,24 @@
|
|||
<script setup>
|
||||
import Body from '@/components/Body.vue'
|
||||
import Error from '@/components/Error.vue'
|
||||
import Header from '@/components/Header.vue'
|
||||
import Login from '@/components/Login.vue'
|
||||
import Main from '@/components/Main.vue'
|
||||
import { useFetch } from '@vueuse/core'
|
||||
import { useAppStore } from '@/store.js'
|
||||
const store = useAppStore()
|
||||
useFetch('/status', {
|
||||
afterFetch(response) {
|
||||
store.steamStatus = response.data
|
||||
return response
|
||||
}
|
||||
}).get().json()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Suspense>
|
||||
<main>
|
||||
<Error />
|
||||
<div class="container-fluid min-vh-100 d-flex flex-column bg-light">
|
||||
<Header />
|
||||
<Body />
|
||||
</div>
|
||||
<Login v-if="! store.steamStatus.loggedIn" />
|
||||
<Main v-else />
|
||||
</main>
|
||||
</Suspense>
|
||||
</template>
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
<script setup>
|
||||
import { onMounted } from 'vue'
|
||||
import { watch } from 'vue'
|
||||
import { Modal } from 'bootstrap'
|
||||
import { useAppStore } from '@/stores/app.js'
|
||||
import { useAppStore } from '@/store.js'
|
||||
const store = useAppStore()
|
||||
let modal = {}
|
||||
onMounted(() => {
|
||||
watch(() => store.errorText, () => {
|
||||
modal = new Modal('#errorModal', {})
|
||||
// modal.show()
|
||||
if (store.errorText) {
|
||||
modal.show()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
@ -3,38 +3,43 @@ import Search from '@/components/Search.vue'
|
|||
import Status from '@/components/Status.vue'
|
||||
import Servers from '@/components/Servers.vue'
|
||||
import { useFetch } from '@vueuse/core'
|
||||
import { useAppStore } from '@/stores/app.js'
|
||||
import { useAppStore } from '@/store.js'
|
||||
const store = useAppStore()
|
||||
import { config } from '@/config'
|
||||
const { error, data } = await useFetch(config.baseUrl + '/status').get().json()
|
||||
const set = (w, e) => {
|
||||
store.section = w
|
||||
const active = Array.from(document.getElementsByClassName('active'))
|
||||
active.forEach((a) => a.classList.remove('active'))
|
||||
e.target.classList.add('active')
|
||||
}
|
||||
async function base() {
|
||||
let which = '/installbase'
|
||||
if (store.steamStatus.installed) {
|
||||
which = '/updatebase'
|
||||
}
|
||||
const { data } = await useFetch(which).get().json()
|
||||
store.errorText = data.value.message
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="data" class="row">
|
||||
<div v-if="store.steamStatus" class="row">
|
||||
<div class="col-3 text-center">
|
||||
<h1>DayZ Docker Server</h1>
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<button
|
||||
@click="installbase"
|
||||
:class="'btn btn-sm ' + (data.installed ? 'btn-danger' : 'btn-success')"
|
||||
@click="base"
|
||||
class="btn btn-sm btn-success"
|
||||
>
|
||||
Install Server Files
|
||||
{{ store.steamStatus.installed ? "Update" : "Install" }} Server Files
|
||||
</button>
|
||||
<button @click="updatebase" class="btn btn-sm btn-outline-success">Update Server Files</button>
|
||||
<button @click="updatemods" class="btn btn-sm btn-outline-success">Update Mods</button>
|
||||
<button type="button" @click="set('servers', $event)" class="btn btn-sm btn-outline-primary">Servers</button>
|
||||
<button type="button" @click="set('mods', $event)" class="btn btn-sm btn-outline-primary active" data-bs-toggle="button">Mods</button>
|
||||
<button type="button" @click="set('search', $event)" class="btn btn-sm btn-outline-primary">Search</button>
|
||||
</div>
|
||||
<Search />
|
||||
<Status :status="data" />
|
||||
<Status />
|
||||
<Servers />
|
||||
</div>
|
||||
</template>
|
||||
|
|
34
web/docroot/src/components/Login.vue
Normal file
34
web/docroot/src/components/Login.vue
Normal file
|
@ -0,0 +1,34 @@
|
|||
<script setup>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="middle">
|
||||
<h1>Login to Steam</h1>
|
||||
<form name="form" method="POST" action="/login" enctype="application/x-www-form-urlencoded">
|
||||
<div>
|
||||
<input type="text" name="username" placeholder="Username" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="password" name="password" placeholder="Password" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="text" name="guard" placeholder="Steam Guard" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" name="remember" id="remember" />
|
||||
<label for="remember">Remember me</label>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" name="submit">Login</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.middle {
|
||||
margin: 0 auto;
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
11
web/docroot/src/components/Main.vue
Normal file
11
web/docroot/src/components/Main.vue
Normal file
|
@ -0,0 +1,11 @@
|
|||
<script setup>
|
||||
import Body from './Body.vue'
|
||||
import Header from './Header.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container-fluid min-vh-100 d-flex flex-column bg-light">
|
||||
<Header />
|
||||
<Body />
|
||||
</div>
|
||||
</template>
|
|
@ -1,7 +1,7 @@
|
|||
<script setup>
|
||||
import { useFetch } from "@vueuse/core"
|
||||
import XmlFile from '@/components/XmlFile.vue'
|
||||
import { useAppStore } from '@/stores/app.js'
|
||||
import { useAppStore } from '@/store.js'
|
||||
const store = useAppStore()
|
||||
import { config } from '@/config'
|
||||
const { data, error } = useFetch(() => config.baseUrl + `/mod/${store.modId}`, {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script setup>
|
||||
import { config } from '@/config'
|
||||
import { useFetch } from '@vueuse/core'
|
||||
import { useAppStore } from '@/stores/app.js'
|
||||
import { useAppStore } from '@/store.js'
|
||||
import ModInfo from '@/components/Modinfo.vue'
|
||||
const store = useAppStore()
|
||||
const { data, error } = useFetch(config.baseUrl + '/mods', {
|
||||
|
@ -17,7 +17,8 @@ const { data, error } = useFetch(config.baseUrl + '/mods', {
|
|||
<div v-if="error" class="row text-danger">
|
||||
{{ error }}
|
||||
</div>
|
||||
<div class="col-md-3 border" v-if="data">
|
||||
<div v-if="store.mods.length === 0">No mods are installed</div>
|
||||
<div v-else class="col-md-3 border" v-if="data">
|
||||
<div>
|
||||
<h4 class="text-center">Installed Mods</h4>
|
||||
<table>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { useAppStore } from '@/stores/app.js'
|
||||
import { useAppStore } from '@/store.js'
|
||||
const store = useAppStore()
|
||||
</script>
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
import { config } from '@/config'
|
||||
import { BKMG } from '@/util'
|
||||
import { useFetch} from '@vueuse/core'
|
||||
import { useAppStore } from '@/stores/app.js'
|
||||
import { useAppStore } from '@/store.js'
|
||||
const store = useAppStore()
|
||||
const { data: searchResults, error, isFetching } = useFetch(() => config.baseUrl + `/search/${store.searchText}`, {
|
||||
const { data: searchResults, error, isFetching } = useFetch(() => `/search/${store.searchText}`, {
|
||||
immediate: false,
|
||||
refetch: true,
|
||||
afterFetch(response) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { useAppStore } from '@/stores/app.js'
|
||||
import { useAppStore } from '@/store.js'
|
||||
const store = useAppStore()
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
<script setup>
|
||||
const { status } = defineProps(['status'])
|
||||
import { useAppStore } from '@/store.js'
|
||||
const store = useAppStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="col">
|
||||
<div>
|
||||
Logged into Steam:
|
||||
<span v-if="store.steamStatus.loggedIn" class="bi bi-check h2 text-success"></span>
|
||||
<span v-else class="bi bi-x h2 danger text-danger"></span>
|
||||
</div>
|
||||
<div>
|
||||
Server files installed:
|
||||
<span v-if="status.installed" class="bi bi-check h2 text-success"></span>
|
||||
<span v-if="store.steamStatus.installed" class="bi bi-check h2 text-success"></span>
|
||||
<span v-else class="bi bi-x h2 danger text-danger"></span>
|
||||
</div>
|
||||
<div v-if="status.version">
|
||||
Version: <span class="text-success fw-bold">{{ status.version }}</span>
|
||||
<span class="text-success fw-bold">({{ status.appid }})</span>
|
||||
<div v-if="store.steamStatus.version">
|
||||
Version: <span class="text-success fw-bold">{{ store.steamStatus.version }}</span>
|
||||
<span class="text-success fw-bold">({{ store.steamStatus.appid }})</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script setup>
|
||||
import { useFetch } from '@vueuse/core'
|
||||
import { config } from '@/config'
|
||||
import { useAppStore } from '@/stores/app.js'
|
||||
import { useAppStore } from '@/store.js'
|
||||
import XmlTree from '@/components/XmlTree.vue'
|
||||
const store = useAppStore()
|
||||
const { data, error } = await useFetch(() => config.baseUrl + `/mod/${store.modId}/${store.modFile}`, {
|
||||
|
|
|
@ -6,7 +6,7 @@ import './css/index.css'
|
|||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import App from './App.vue'
|
||||
import {useAppStore} from "@/stores/app";
|
||||
import {useAppStore} from '@/store'
|
||||
|
||||
// Create an instance of our Vue app
|
||||
const app = createApp(App)
|
||||
|
|
|
@ -5,8 +5,10 @@ export const useAppStore = defineStore('app', {
|
|||
errorText: '',
|
||||
modId: 0,
|
||||
modFile: '',
|
||||
messageText: '',
|
||||
mods: [],
|
||||
searchText: '',
|
||||
section: 'mods',
|
||||
steamStatus: {appid: 0, installed: false, loggedIn: false, version: ''},
|
||||
})
|
||||
})
|
|
@ -8,6 +8,6 @@
|
|||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="src/main.js"></script>
|
||||
<script type="module" src="docroot/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -7,20 +7,12 @@
|
|||
but to also make them available for the creation of server containers.
|
||||
*/
|
||||
import express from 'express'
|
||||
import ViteExpress from 'vite-express'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import https from 'https'
|
||||
import { spawn } from 'child_process'
|
||||
|
||||
const app = express()
|
||||
|
||||
app.use((req, res, next) => {
|
||||
res.append('Access-Control-Allow-Origin', ['*'])
|
||||
res.append('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE')
|
||||
res.append('Access-Control-Allow-Headers', 'Content-Type')
|
||||
next()
|
||||
})
|
||||
|
||||
/*
|
||||
The DayZ server Steam app ID. USE ONE OR THE OTHER!!
|
||||
|
||||
|
@ -28,12 +20,12 @@ app.use((req, res, next) => {
|
|||
Meanwhile, if we have a release-compatible binary, the base files must be installed from this id,
|
||||
even if the server binary and required shared objects don't come from it. (They'd come from...elsewhere...)
|
||||
*/
|
||||
//const server_appid = "223350"
|
||||
const server_appid = "223350"
|
||||
|
||||
/*
|
||||
Without a release binary, we must use the experimental server app ID.
|
||||
*/
|
||||
const server_appid = "1042420"
|
||||
// const server_appid = "1042420"
|
||||
|
||||
/*
|
||||
DayZ release client Steam app ID. This is for mods, as only the release client has them.
|
||||
|
@ -54,6 +46,7 @@ const appid_version = versions[server_appid]
|
|||
*/
|
||||
const modDir = "/mods"
|
||||
const serverFiles = "/serverfiles"
|
||||
const homeDir = "/home/user"
|
||||
|
||||
/*
|
||||
File path delimiter
|
||||
|
@ -115,16 +108,14 @@ const allConfigFiles = {
|
|||
|
||||
const config = {
|
||||
installFile: serverFiles + "/DayZServer",
|
||||
loginFile: homeDir + "/steamlogin",
|
||||
modDir: modDir + "/" + client_appid,
|
||||
port: 8000,
|
||||
steamAPIKey: process.env["STEAMAPIKEY"]
|
||||
}
|
||||
|
||||
const getVersion = (installed) => {
|
||||
if(installed) {
|
||||
return "1.22.bogus"
|
||||
}
|
||||
return ""
|
||||
const getVersion = () => {
|
||||
return "1.25.bogus"
|
||||
}
|
||||
|
||||
const getDirSize = (dirPath) => {
|
||||
|
@ -175,29 +166,77 @@ 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)
|
||||
const steamcmd = async (args) => {
|
||||
let out = ''
|
||||
let err = ''
|
||||
const command = 'steamcmd +force_install_dir ' + serverFiles + ' ' + args + ' +quit'
|
||||
// console.log(command)
|
||||
const proc = spawn(command, {shell: true})
|
||||
proc.stdout.on('data', (data) => {
|
||||
res.write(data)
|
||||
// console.log("[OUT] " + data)
|
||||
out += data + "\n"
|
||||
})
|
||||
proc.stderr.on('data', (data) => {
|
||||
res.write(data)
|
||||
// console.log("[ERROR] " + data)
|
||||
err += data + "\n"
|
||||
})
|
||||
proc.on('error', (error) => {
|
||||
res.write(error)
|
||||
// console.log("[ERROR] " + error)
|
||||
err += error + "\n"
|
||||
})
|
||||
proc.on('close', (error) => {
|
||||
if(error) res.write(error)
|
||||
res.end()
|
||||
if(error) err += error
|
||||
// console.log("Return")
|
||||
return { out: out, err: err }
|
||||
})
|
||||
}
|
||||
|
||||
app.use(express.static('root'))
|
||||
const app = express()
|
||||
|
||||
app.use(express.json())
|
||||
app.use(express.urlencoded({extended: true}))
|
||||
|
||||
app.disable('etag')
|
||||
|
||||
app.use((req, res, next) => {
|
||||
res.append('Access-Control-Allow-Origin', ['*'])
|
||||
res.append('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE')
|
||||
res.append('Access-Control-Allow-Headers', 'Content-Type')
|
||||
next()
|
||||
})
|
||||
|
||||
// Install a mod
|
||||
app.get(('/install/:modId'), (req, res) => {
|
||||
const modId = req.params["modId"]
|
||||
// Shell out to steamcmd, monitor the process, and display the output as it runs
|
||||
res.send(modId + " was installed")
|
||||
})
|
||||
|
||||
// Install base files
|
||||
app.get('/installbase', (req, res) => {
|
||||
const ret = {
|
||||
"message": "Base files were installed"
|
||||
}
|
||||
res.send(ret)
|
||||
})
|
||||
|
||||
// Login to Steam
|
||||
app.post(('/login'), async (req, res) => {
|
||||
const username = req.body?.username;
|
||||
const password = req.body?.password;
|
||||
const guard = req.body?.guard;
|
||||
const remember = req.body?.remember;
|
||||
let args = `+login "${username}" "${password}"`
|
||||
if (guard) args += ` "${guard}"`
|
||||
const result = await steamcmd(args)
|
||||
console.log(result)
|
||||
if (result) {
|
||||
fs.writeFileSync(config.loginFile, username)
|
||||
res.send(1)
|
||||
} else {
|
||||
res.send(0)
|
||||
}
|
||||
})
|
||||
|
||||
// Get mod metadata by ID
|
||||
app.get('/mod/:modId', (req, res) => {
|
||||
|
@ -224,6 +263,24 @@ app.get('/mod/:modId/:file', (req, res) => {
|
|||
}
|
||||
})
|
||||
|
||||
/*
|
||||
Get all mod metadata
|
||||
*/
|
||||
app.get('/mods', (req, res) => {
|
||||
const mods = getMods()
|
||||
const ret = {
|
||||
"mods": mods
|
||||
}
|
||||
res.send(ret)
|
||||
})
|
||||
|
||||
// Remove a mod
|
||||
app.get(('/remove/:modId'), (req, res) => {
|
||||
const modId = req.params["modId"]
|
||||
// Shell out to steamcmd, monitor the process, and display the output as it runs
|
||||
res.send(modId + " was removed")
|
||||
})
|
||||
|
||||
// Search for a mod
|
||||
app.get(('/search/:searchString'), (req, res) => {
|
||||
const searchString = req.params["searchString"]
|
||||
|
@ -241,59 +298,40 @@ app.get(('/search/:searchString'), (req, res) => {
|
|||
})
|
||||
})
|
||||
|
||||
// Install a mod
|
||||
app.get(('/install/:modId'), (req, res) => {
|
||||
const modId = req.params["modId"]
|
||||
// Shell out to steamcmd, monitor the process, and display the output as it runs
|
||||
res.send(modId + " was installed")
|
||||
})
|
||||
|
||||
// Remove a mod
|
||||
app.get(('/remove/:modId'), (req, res) => {
|
||||
const modId = req.params["modId"]
|
||||
// Shell out to steamcmd, monitor the process, and display the output as it runs
|
||||
res.send(modId + " was removed")
|
||||
})
|
||||
|
||||
// Update base files
|
||||
app.get('/updatebase', (req, res) => {
|
||||
login()
|
||||
res.send("Base files were updates")
|
||||
})
|
||||
|
||||
// Update mods
|
||||
app.get('/updatemods', (req, res) => {
|
||||
res.send("Mod files were updates")
|
||||
})
|
||||
|
||||
/*
|
||||
Get the status of things:
|
||||
If the base files are installed, the version of the server, the appid (If release or experimental)
|
||||
*/
|
||||
app.get('/status', (req, res) => {
|
||||
// FIXME Async/await this stuff...
|
||||
app.get('/status', (_, res) => {
|
||||
const installed = fs.existsSync(config.installFile)
|
||||
const version = getVersion(installed)
|
||||
const loggedIn = fs.existsSync(config.loginFile)
|
||||
const ret = {
|
||||
"appid": appid_version,
|
||||
"installed": installed,
|
||||
"version": version
|
||||
"loggedIn": loggedIn,
|
||||
}
|
||||
ret.error = "This is a test error from the back end"
|
||||
res.send(ret)
|
||||
})
|
||||
|
||||
/*
|
||||
Get all mod metadata
|
||||
*/
|
||||
app.get('/mods', (req, res) => {
|
||||
const mods = getMods()
|
||||
const ret = {
|
||||
"mods": mods
|
||||
if (installed) {
|
||||
ret.version = getVersion()
|
||||
}
|
||||
res.send(ret)
|
||||
})
|
||||
|
||||
app.listen(config.port, () => {
|
||||
console.log(`Listening on port ${config.port}`)
|
||||
// Update base files
|
||||
app.get('/updatebase', (req, res) => {
|
||||
res.send("Base files were updated")
|
||||
})
|
||||
|
||||
// Update mods
|
||||
app.get('/updatemods', (req, res) => {
|
||||
res.send("Mod files were updated")
|
||||
})
|
||||
|
||||
ViteExpress.listen(app, config.port, () =>
|
||||
console.log(`Server is listening on port ${config.port}`)
|
||||
)
|
||||
|
||||
// const server = app.listen(config.port, "0.0.0.0", () =>
|
||||
// console.log(`Server is listening on port ${config.port}`)
|
||||
// )
|
||||
//
|
||||
// ViteExpress.bind(app, server)
|
2486
web/package-lock.json
generated
2486
web/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -4,16 +4,26 @@
|
|||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "nodemon index.js -w index.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"express": "^4.18.2"
|
||||
},
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"nodemon": "^2.0.22"
|
||||
"@vitejs/plugin-vue": "^5.1.1",
|
||||
"express": "^4.19.2",
|
||||
"nodemon": "^3.1.4",
|
||||
"vite": "^5.3.5",
|
||||
"vite-express": "^0.17.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@vueuse/core": "^10.11.0",
|
||||
"bootstrap": "^5.3.3",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"pinia": "^2.2.0",
|
||||
"vue": "^3.4.34"
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 81 KiB |
|
@ -1,22 +0,0 @@
|
|||
button {
|
||||
padding: 5px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding-right: 10px
|
||||
}
|
||||
|
||||
|
||||
.selected {
|
||||
background-color: cyan;
|
||||
}
|
||||
|
||||
.simulink {
|
||||
cursor: pointer;
|
||||
text-underline: blue;
|
||||
}
|
||||
|
||||
.simulink:hover {
|
||||
background-color: cyan;
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>DayZ Docker Server</title>
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.png">
|
||||
<!-- 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'
|
||||
createApp(app).mount('#app')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,313 +0,0 @@
|
|||
const template = `
|
||||
<div
|
||||
class="modal"
|
||||
id="errorModal"
|
||||
data-bs-backdrop="static"
|
||||
data-bs-keyboard="false"
|
||||
tabindex="-1"
|
||||
aria-labelledby="errorModalLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="errorModalLabel">Error</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</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>
|
||||
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>
|
||||
<h4 class="text-center">Installed Mods</h4>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Steam Link</th>
|
||||
<th>Mod Info</th>
|
||||
</tr>
|
||||
<template
|
||||
v-for="mod in mods"
|
||||
>
|
||||
<tr>
|
||||
<td>
|
||||
<a
|
||||
target="_blank"
|
||||
:href="steamURL + mod.id"
|
||||
>
|
||||
{{ mod.id }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a class="simulink" @click="getModInfo(mod.id)">{{ mod.name }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-9 border">
|
||||
<div class="d-flex" v-if="modInfo != ''">
|
||||
<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 != ''" class="d-flex">
|
||||
<table>
|
||||
<tr>
|
||||
<th>Steam Link</th>
|
||||
<th>Title</th>
|
||||
<th>Size</th>
|
||||
<th>Last Updated</th>
|
||||
<th>Subscriptions</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
<tr v-for="result in searchResults">
|
||||
<td>
|
||||
<a
|
||||
target="_blank"
|
||||
:href="steamURL + result.publishedfileid"
|
||||
>
|
||||
<img :alt="result.short_description" data-bs-toggle="tooltip" data-bs-placement="left" :title="result.short_description" width="160" height="90" :src="result.preview_url">
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ result.title }}</td>
|
||||
<td>{{ BKMG(result.file_size) }}</td>
|
||||
<td>{{ new Date(result.time_updated * 1000).toLocaleDateString("en-us") }}</td>
|
||||
<td>{{ result.lifetime_subscriptions }}</td>
|
||||
<td>
|
||||
<button v-if="mods.find(o => o.id == result.publishedfileid)" @click="removeMod(result.publishedfileid)" type="button" class="btn btn-danger">Remove</button>
|
||||
<button v-else @click="installMod(result.publishedfileid)" type="button" class="btn btn-success">Install</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</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: "",
|
||||
installed: false,
|
||||
mods: [],
|
||||
modInfo: "",
|
||||
searchResults: [],
|
||||
steamURL: 'https://steamcommunity.com/sharedfiles/filedetails/?id=',
|
||||
version: "Unknown",
|
||||
XMLFile: "",
|
||||
XMLInfo: "",
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getModInfo(modId) {
|
||||
fetcher ({
|
||||
url: '/mod/' + modId,
|
||||
type: "json",
|
||||
callback: (response) => {
|
||||
this.modInfo = response
|
||||
this.XMLInfo = ""
|
||||
this.searchResults = ""
|
||||
}
|
||||
})
|
||||
},
|
||||
getXMLInfo(modId, file) {
|
||||
for (const e of document.getElementsByClassName("selected")) e.classList.remove("selected")
|
||||
fetch('/mod/' + modId + '/' + file)
|
||||
.then(response => response.text())
|
||||
.then(response => {
|
||||
this.XMLFile = file
|
||||
this.XMLInfo = response
|
||||
for (const e of document.getElementsByClassName(file)) e.classList.add("selected")
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
this.fetchError = error.message
|
||||
})
|
||||
},
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
fetch('/search/' + e.target.search.value)
|
||||
.then(response => response.json())
|
||||
.then(response => {
|
||||
this.modInfo = ""
|
||||
this.XMLInfo = ""
|
||||
// const sortField = "time_updated"
|
||||
const sortField = "lifetime_subscriptions"
|
||||
response.response.publishedfiledetails.sort((a, b) =>
|
||||
a[sortField] < b[sortField] ? 1 : -1
|
||||
)
|
||||
this.searchResults = response.response.publishedfiledetails
|
||||
})
|
||||
.then(() => {
|
||||
// Enable all tooltips
|
||||
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl)
|
||||
})
|
||||
// Enable all alerts
|
||||
$('.alert').alert()
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
this.fetchError = error.message
|
||||
})
|
||||
},
|
||||
installMod(modId) {
|
||||
fetch('/install/' + modId)
|
||||
.then(response => response.text())
|
||||
.then(response => {
|
||||
console.log(response)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
this.fetchError = error.message
|
||||
})
|
||||
},
|
||||
removeMod(modId) {
|
||||
fetch('/remove/' + modId)
|
||||
.then(response => response.text())
|
||||
.then(response => {
|
||||
console.log(response)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
this.fetchError = error.message
|
||||
})
|
||||
},
|
||||
BKMG(val) {
|
||||
const units = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
|
||||
let l = 0, n = parseInt(val, 10) || 0
|
||||
while(n >= 1024 && ++l){
|
||||
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() {
|
||||
// Get the data
|
||||
fetch('/status')
|
||||
.then(response => response.json())
|
||||
.then(response => {
|
||||
this.installed = response.installed
|
||||
this.version = response.version
|
||||
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('errorModal'))
|
||||
modal.show()
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
this.fetchError = error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
{ "result": 1, "publishedfileid": "2489240546", "creator": "76561199068873691", "creator_appid": 221100, "consumer_appid": 221100, "consumer_shortcutid": 0, "filename": "", "file_size": "276817803", "preview_file_size": "27678", "preview_url": "https://steamuserimages-a.akamaihd.net/ugc/2011465736408144669/A7137390FBB9F4F94E0BFE5389932F6DE7AB7B87/", "url": "", "hcontent_file": "4050838808220661564", "hcontent_preview": "2011465736408144669", "title": "LastDayZ_Helis", "short_description": "The author of the helicopter mod https://sibnic.info on the site you can download the latest version of free helicopters, If you need help with installation, go to discord https://sibnic.info/discord", "time_created": 1621186063, "time_updated": 1684985831, "visibility": 0, "flags": 5632, "workshop_file": false, "workshop_accepted": false, "show_subscribe_all": false, "num_comments_public": 0, "banned": false, "ban_reason": "", "banner": "76561197960265728", "can_be_deleted": true, "app_name": "DayZ", "file_type": 0, "can_subscribe": true, "subscriptions": 7935, "favorited": 3, "followers": 0, "lifetime_subscriptions": 22759, "lifetime_favorited": 5, "lifetime_followers": 0, "lifetime_playtime": "0", "lifetime_playtime_sessions": "0", "views": 535, "num_children": 0, "num_reports": 0, "tags": [ { "tag": "Animation", "display_name": "Animation" }, { "tag": "Environment", "display_name": "Environment" }, { "tag": "Sound", "display_name": "Sound" }, { "tag": "Vehicle", "display_name": "Vehicle" }, { "tag": "Mod", "display_name": "Mod" } ], "language": 0, "maybe_inappropriate_sex": false, "maybe_inappropriate_violence": false, "revision_change_number": "14", "revision": 1, "ban_text_check_result": 5 }
|
||||
|
||||
*/
|
|
@ -1,103 +0,0 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +1,17 @@
|
|||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
cacheDir: '/tmp/vite',
|
||||
plugins: [vue()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
'@': '/web/docroot/src'
|
||||
}
|
||||
},
|
||||
server: {
|
||||
port: 8001
|
||||
fs: {
|
||||
allow: ['/node_modules', '/web']
|
||||
}
|
||||
}
|
||||
})
|
Loading…
Add table
Reference in a new issue