diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 0cf07bf..0000000 --- a/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -** -!files \ No newline at end of file diff --git a/.gitignore b/.gitignore index 485dee6..cf9cb08 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ .idea +*.iml +node_modules/ diff --git a/docker-compose.yml b/docker-compose.yml index 9751e41..8f0f54c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,19 +1,53 @@ version: "3.3" -# This is where the server files, profile, mods, and logs will reside. -# The server script does its best to clean up the copious logs that the -# server creates, but with mods, this volume will grow rather large. -# It's best to keep an eye on its size. volumes: - homedir: + # For steamcmd files and resource files used by the scripts + homedir_main: + # For Steam, for now + homedir_server: + # Where the server files will be installed + serverfiles: + # Server profile files + profiles: + # Upstream mission files + servermpmissions: + # Server mission files + mpmissions: + # Mods + mods: services: - main: - build: . + web: + build: web volumes: - - homedir:/home/user + - homedir_main:/home/user + - serverfiles:/serverfiles + - servermpmissions:/serverfiles/mpmissions + - mods:/serverfiles/steamapps/workshop/content + - mods:/mods - ./files:/files + - ./web/bin/dz:/usr/local/bin/dz + - ./web:/web + ports: + - "8000:8000/tcp" + restart: no + environment: + # The use of the Steam API requires a key. Get yours here: https://steamcommunity.com/dev/apikey + - STEAMAPIKEY=YOUR_STEAM_API_KEY_HERE + + server: + build: server + volumes: + - homedir_server:/home/user + - serverfiles:/serverfiles + - servermpmissions:/mpmissions:ro + - mods:/mods + - mpmissions:/serverfiles/mpmissions + - profiles:/profiles + - ./files:/files + - ./server:/server + - ./server/bin/dz:/usr/local/bin/dz # To have the server show up in the LAN tab of the DayZ launcher, # it must run under host mode. network_mode: host @@ -21,20 +55,19 @@ services: # 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 -# - 2303:2303/udp -# - 2304:2304/udp +# # RCON port +# - 2302:2302/udp +# # Steam port # - 27016:27016/udp # Always restart, unless stopped restart: unless-stopped # Allows attaching a debugger from the host -# cap_add: -# - SYS_PTRACE + cap_add: + - SYS_PTRACE # Allows core files to be created within the container. These are VERY LARGE! Enable only for debugging! -# ulimits: -# core: -# soft: -1 -# hard: -1 - # Do nothing instead of starting the server, which is the default. - # Helpful for development or debugging. -# command: tail -f /dev/null + ulimits: + core: + soft: -1 + hard: -1 diff --git a/files/bin/dz-common b/files/bin/dz-common new file mode 100755 index 0000000..f3a6969 --- /dev/null +++ b/files/bin/dz-common @@ -0,0 +1,135 @@ +#!/usr/bin/env bash + +set -eEa + +# If you want/need the server and rcon ports to be different, set them here. +# The steam query port is set in serverDZ.cfg. + +# Server port +port=2302 +rcon_port=2303 + +# Don't change anything else. + +# Colors +default="\e[0m" +red="\e[31m" +green="\e[32m" +yellow="\e[93m" +lightblue="\e[94m" +blue="\e[34m" +magenta="\e[35m" +cyan="\e[36m" + +# DayZ release server Steam app ID. USE ONE OR THE OTHER!! +# Presumably once the Linux server is released, the binaries will come from this ID. +# But more importantly, if we have a release-compatible binary, the base files must be installed from this id, +# even if the server binary and accompanying shared object don't come from it. +#release_server_appid=223350 +# Without a release binary, we must use the experimental server app id for everything. +release_server_appid=1042420 + +# DayZ release client SteamID. This is for mods, as only the release client has them. +release_client_appid=221100 + +# Common container base directories +FILES="/files" +SERVER_FILES="/serverfiles" + +# Used to check if dayZ is installed +SERVER_INSTALL_FILE="${SERVER_FILES}/DayZServer" + +# Steam files +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 + read -s -n 1 a + a=$(echo ${a} | tr A-Z a-z) + echo + if [[ "${a}" = "y" ]] + then + return 0 + else + 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 +} diff --git a/files/mods/install.sh b/files/bin/install.sh similarity index 66% rename from files/mods/install.sh rename to files/bin/install.sh index 9003d59..3ce30b1 100755 --- a/files/mods/install.sh +++ b/files/bin/install.sh @@ -5,26 +5,26 @@ set -eE source /files/mods/${1}/install.env echo -if echo ${0} | grep -q "uninstall.sh" +if [[ ${2} = "uninstall" ]] then echo "Backing up, as uninstalling will remove the ${MAP} mpmissions directory" dayzserver backup echo "Uninstalling mpmissions..." echo - rm -rf ${HOME}/serverfiles/mpmissions/${MPDIR} -elif echo ${0} | grep -q "update.sh" + rm -rf ${SERVER_FILES}/mpmissions/${MPDIR} +elif [[ ${2} = "update" ]] then echo "Updating mpmissions directory..." echo cd /tmp git clone ${REPO} 2> /dev/null 1> /dev/null - cp -a ${DIR}/${MPDIR} ${HOME}/serverfiles/mpmissions + cp -a ${DIR}/${MPDIR} ${SERVER_FILES}/mpmissions rm -rf ${DIR} else echo "Installing mpmissions files..." echo cd /tmp git clone ${REPO} 2> /dev/null 1> /dev/null - cp -a ${DIR}/${MPDIR} ${HOME}/serverfiles/mpmissions + cp -a ${DIR}/${MPDIR} ${SERVER_FILES}/mpmissions rm -rf ${DIR} fi diff --git a/files/bin/xml.sh b/files/bin/xml.sh new file mode 100755 index 0000000..6b50a0c --- /dev/null +++ b/files/bin/xml.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +# A generic script that retrieves XML files from mods, either upstream in remote endpoints, or +# locally from the downloaded mod directory + +set -eE + +ID=${1} + +source ${FILES}/mods/${ID}/xml.env + +# Iterate over the file names we can handle +for var in CFGEVENTSPAWNS CFGSPAWNABLETYPES EVENTS TYPES +do + if echo ${!var} | grep -q http + then + OUT="${WORKSHOP_DIR}/${ID}/${var,,}.xml" + echo "${var} is a URL, downloading to ${OUT}" + curl -so ${OUT} ${!var} + xmllint --noout ${OUT} 2> /dev/null || { + echo -e "${red}${var,,}.xml does not pass XML lint test!${default}" + } && { + echo -e "${green}${var,,}.xml passes XML lint test!${default}" + } + fi +done diff --git a/files/dayzserver b/files/dayzserver deleted file mode 100755 index 3203b0c..0000000 --- a/files/dayzserver +++ /dev/null @@ -1,743 +0,0 @@ -#!/usr/bin/env bash - -set -eEa - -# If you want/need the server and rcon ports to be different, set them here. -# The steam query port is set in serverDZ.cfg. - -# Server port -port=2302 -rcon_port=2303 - -# Don't change anything else. - -# Colors -default="\e[0m" -red="\e[31m" -green="\e[32m" -yellow="\e[93m" -lightblue="\e[94m" -blue="\e[34m" -magenta="\e[35m" -cyan="\e[36m" - -# DayZ release server Steam app ID. Presumably once the Linux server is released, the binaries will come -# from this ID. Let's find out! -#release_server_appid=223350 - -# For now, use the experimental server app id -release_server_appid=1042420 - -# DayZ release client SteamID. This is for mods, as only the release client has them. -release_client_appid=221100 - -# Base directories -CFG_SRC_FILES="/files" -SERVER_FILES="${HOME}/serverfiles" -SERVER_PROFILE="${HOME}/profiles" - -mkdir -p ${SERVER_FILES}/battleye ${SERVER_PROFILE} - -# Server configuration file -SERVER_CFG_FILE="serverDZ.cfg" -SERVER_CFG_DST="${SERVER_FILES}/${SERVER_CFG_FILE}" -SERVER_CFG_SRC="${CFG_SRC_FILES}/${SERVER_CFG_FILE}" - -# Command line parameters except mod, as that is handled separately. -parameters="-config=${SERVER_CFG_FILE} -port=${port} -freezecheck -BEpath=${SERVER_FILES}/battleye -profiles=${SERVER_PROFILE} -nologs" - -# Used to check if dayZ is installed -SERVER_INSTALL_FILE="${SERVER_FILES}/DayZServer" - -# Steam files -STEAM_LOGIN="${HOME}/steamlogin" -STEAMCMD=steamcmd - -# Workshop. This file will store metadata about what mods are installed. -WORKSHOP_CFG="${HOME}/workshop.cfg" -if [ ! -f "${WORKSHOP_CFG}" ] -then - touch "${WORKSHOP_CFG}" -fi - -# An array to store Workshop items. Each element contains the mod's ID, name, and state (active or not). -declare -a workshopID -workshopfolder="${SERVER_FILES}/steamapps/workshop/content/${release_client_appid}" - -# Backups -BACKUP_DIR="${HOME}/backup" -if [ ! -d "${BACKUP_DIR}" ] -then - mkdir -p "${BACKUP_DIR}" -fi - -# Other stuff -YES="${green}yes${default}" -NO="${red}no${default}" - -# Functions - -# Usage -usage(){ - echo -e " -${red}Bad option or arguments! ${yellow}${*}${default} - -Usage: ${green}$(basename $0)${yellow} option [ arg1 [ arg2 ] ] - -Options and arguments: - - a|activate id - Activate an installed DayZ Workshop items by id or index - add id - Add a DayZ Workshop item by id. Added items become active by default - b|backup - Backup the mission storage files in all mission directories - c|config - Update the internal serverDZ.cfg file from files/serverDZ.cfg on the host. Presents a unified diff if the internal file doesn't match the host file - d|deactivate id - Deactivate an installed DayZ Workshop items by id or index - Keeps the mod files but excludes it from the mod parameter - f|force - Forcibly kill the server. Use only as a last resort if the server won't shut down - i|install - Install the DayZ server files - l|list - List Workshop items and their details - g|login - Login to Steam. - m|modupdate - Update the mod files - n|rcon - Connect to the server using a python RCON client - r|remove id - Remove all files and directories of a Workshop item by id - restart - Restart the server without restarting the container - s|status - Shows the server's status: Running, uptime, mods, parameters, mod parameter, etc. - stop - Stop the server - u|update - Update the server files -${default}" - exit 1 -} - -# Make sure to clean up and report on exit, as these files remain in the container's volume -report() { - rm -f /tmp/mod_command_line /tmp/parameters - echo - echo -e "${yellow}========================================== error.log ==========================================" - find "${HOME}" -name error.log -exec head {} \; -exec tail -n 30 {} \; -exec rm -f {} \; - echo - echo -e "========================================== script*.log ========================================" - find "${HOME}" -name "script*.log" -exec head {} \; -exec tail -n 30 {} \; -exec rm -f {} \; - echo - echo -e "========================================== *.RPT ==============================================" - find "${HOME}" -name "*.RPT" -exec ls -la {} \; -exec tail -n 30 {} \; -exec rm -f {} \; - echo - echo -e "========================================== End log ======================================${default}" -} - -# Convenience function -prompt_yn(){ - echo -n "${1} (y|N) " >&2 - read -s -n 1 a - a=$(echo ${a} | tr A-Z a-z) - echo - if [[ "${a}" = "y" ]] - then - return 0 - else - return 1 - fi -} - -check_install(){ - if [ ! -f "${SERVER_INSTALL_FILE}" ] - then - echo - echo -e "The DayZ server files are not installed. Run '${green}docker-compose run --rm main dayzserver install${default}'" - echo - exit 1 - fi -} - -# Ensures all is installed and ready before allowing operations that depends on things being ready. -# Installs the initial server config file from its template. -# Handles the importing of changes to that template. -# Installs the initial Battleye RCON config. -loadconfig(){ -# check_install - # Handle the initial server configuration file - if [ ! -f ${SERVER_CFG_DST} ] - then - echo "Creating initial server configuration file" - cp "${SERVER_CFG_SRC}" "${SERVER_CFG_DST}" - fi - # battleye config and rconpassword setup - # The server creates a new file from this file, which it then uses. - # Let's make sure to delete it first - BE_SERVER_FILE="${HOME}/serverfiles/battleye/beserver_x64.cfg" - ALT_BE_SERVER_FILE=$(find ${HOME}/serverfiles/battleye -name "beserver_x64_active*") - if [ ! -f "${BE_SERVER_FILE}" ] && [ ! -f "${ALT_BE_SERVER_FILE}" ] - then - passwd=$(openssl rand -base64 8 | tr -dc 'A-Za-z0-9') - if [ "${passwd}" == "" ] - then - passwd=$(< /dev/urandom tr -dc 'A-Za-z0-9' | head -c10) - fi - if [ "${passwd}" == "" ] - then - printf "[ ${red}FAIL${default} ] Could not generate a passwort for RCON!\nOpen the Battleye config with 'dayzserver rcon'." - exit 1 - else - cat > "${BE_SERVER_FILE}" < ~/py3rcon.config.json -} - -# Start the server in the foreground -start(){ - # Do the report on exit. Set here so that it only happens once we're starting the server, and not for other actions. - trap ' - report - ' EXIT - mod_cmd - cd ${SERVER_FILES} - # Run the server. Allow docker to restart the container if the script exits with a code other than 0. This is so we can - # safely shut the container down without killing the server within. - printf "[ ${green}DayZ${default} ] Server starting...\n" - # Save the mod command line and parameters that were used to start the server, so status reflects the running server's - # actual status with those - echo ${mod_command_line} > /tmp/mod_command_line - echo ${parameters} > /tmp/parameters - ./DayZServer ${mod_command_line} ${parameters} - EXIT_CODE=$? - if [ -f ${SERVER_FILES}/restart ] - then - rm -f ${SERVER_FILES}/restart - EXIT_CODE=42 - fi - printf "\n[ ${yellow}DayZ${default} ] Server exited. Exit code: ${EXIT_CODE}\n" - exit ${EXIT_CODE} -} - -# Restarts the server by forcing an exit code other than 0, causing docker to restart the container. -restart(){ - touch "${SERVER_FILES}/restart" - echo "Restarting DayZ server..." - kill -TERM $(pidof DayZServer) -} - -# Stops the server cleanly and exits 0, which will stop the container. -stop(){ - echo "Stopping DayZ server..." - kill -TERM $(pidof DayZServer) -} - -# Forcibly kill the server, should it be necessary. -force(){ - echo "Forcibly stopping DayZ server..." - kill -KILL $(pidof DayZServer) -} - -# Hanle the Steam login information. -login(){ - loadconfig - if [ -f "${STEAM_LOGIN}" ] - then - if prompt_yn "The steam login is already set. Reset it?" - then - rm -f "${STEAM_LOGIN}" - else - echo "Not reset." - exit 0 - fi - fi - if [ ! -f "${STEAM_LOGIN}" ] - then - echo "Setting up Steam credentials" - echo -n "Steam Username (anonymous): " - read steamlogin - if [[ "${steamlogin}" = "" ]] - then - echo "Steam login set to 'anonymous'" - steamlogin="anonymous" - fi - echo "steamlogin=${steamlogin}" > "${STEAM_LOGIN}" - ${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" +quit - fi -} - -# "Perform" the Steam login. This just sources the file with the Steam login name. -dologin(){ - loadconfig - if [ -f "${STEAM_LOGIN}" ] - then - source "${STEAM_LOGIN}" - else - echo "No cached Steam credentials. Please configure this now: " - login - fi -} - -# Perform the installation of the server files. -install(){ - loadconfig - if [ ! -f "${SERVER_INSTALL_FILE}" ] || [[ ${1} = "force" ]] - then - mkdir -p "${SERVER_FILES}" - mkdir -p "${SERVER_PROFILE}" - printf "[ ${yellow}DayZ${default} ] Downloading DayZ Server-Files!\n" - dologin - ${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" +app_update "${release_server_appid}" validate +quit - else - printf "[ ${lightblue}DayZ${default} ] The server is already installed.\n" - fi -} - -# Handle any changes in the server config file by allowing them to be merged after viewing a diff. -config(){ - if ! diff -q "${SERVER_CFG_DST}" "${SERVER_CFG_SRC}" - then - echo "=========================================================================" - diff -Nau --color "${SERVER_CFG_DST}" "${SERVER_CFG_SRC}" | more - echo "=========================================================================" - if prompt_yn "The new server configuration file differs from what's installed. Use it?" - then - echo "Updating the server configuration file" - cp "${SERVER_CFG_SRC}" "${SERVER_CFG_DST}" - else - echo "NOT updating the server configuration file" - fi - else - echo "No differences found between ${SERVER_CFG_SRC} and ${SERVER_CFG_DST}" - fi -} - -# Update the server files. -update(){ - dologin - appmanifestfile=${SERVER_FILES}/steamapps/appmanifest_"${release_server_appid}".acf - printf "[ ... ] Checking for update:" - # gets currentbuild - currentbuild=$(grep buildid "${appmanifestfile}" | tr '[:blank:]"' ' ' | tr -s ' ' | cut -d \ -f3) - # Removes appinfo.vdf as a fix for not always getting up to date version info from SteamCMD - if [ -f "${HOME}/Steam/appcache/appinfo.vdf" ] - then - rm -f "${HOME}/Steam/appcache/appinfo.vdf" - fi - # check for new build - availablebuild=$(${STEAMCMD} +login "${steamlogin}" +app_info_update 1 +app_info_print "${release_server_appid}" +quit | \ - sed -n '/branch/,$p' | grep -m 1 buildid | tr -cd '[:digit:]') - if [ -z "${availablebuild}" ] - then - printf "\r[ ${red}FAIL${default} ] Checking for update:\n" - printf "\r[ ${red}FAIL${default} ] Checking for update:: Not returning version info\n" - exit - else - printf "\r[ ${green}OK${default} ] Checking for update:" - fi - # compare builds - if [ "${currentbuild}" != "${availablebuild}" ] || [[ ${1} = "force" ]] - then - printf "\r[ ${green}OK${default} ] Checking for update:: Update available\n" - printf "Update available:\n" - printf "\tCurrent build: ${red}${currentbuild}${default}\n" - printf "\tAvailable build: ${green}${availablebuild}${default}\n" - printf "\thttps://steamdb.info/app/${release_server_appid}/\n" - printf "\nApplying update" - # run update - dologin - ${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" +app_update "${release_server_appid}" validate +quit - modupdate - else - printf "\r[ ${green}OK${default} ] Checking for update:: No update available\n" - printf "\nNo update available:\n" - printf "\tCurrent version: ${green}${currentbuild}${default}\n" - printf "\tAvailable version: ${green}${availablebuild}${default}\n" - printf "\thttps://steamdb.info/app/${release_server_appid}/\n\n" - fi -} - -# Assemble the workshop list variable -get_mods(){ - mapfile -t workshopID < "${WORKSHOP_CFG}" - workshoplist="" - for i in "${workshopID[@]}" - do - ID=$(echo ${i} | cut -d: -f1) - workshoplist+=" +workshop_download_item "${release_client_appid}" "${ID} - done -} - -get_mod_name(){ - if [ -d "${workshopfolder}/${1}" ] - then - grep name ${workshopfolder}/${1}/meta.cpp | cut -d '"' -f2 | sed -r 's/\s+//g' - fi -} - -# Update mods -modupdate(){ - get_mods - echo "Updating mods..." - dologin -# echo ${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" ${workshoplist} +quit - ${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" ${workshoplist} +quit - # Updated files come in with mixed cases. Fix that. - echo -ne "\nFixing file names..." - find "${workshopfolder}" -depth -exec rename -f 's/(.*)\/([^\/]*)/$1\/\L$2/' {} \; - echo "done" - echo -} - -# Add a mod -add(){ - if [ -d "${workshopfolder}/${1}" ] - then - echo -e "${yellow}Warning: The mod directory ${workshopfolder}/${1} already exists!${default}" - MODNAME=$(get_mod_name ${1}) - fi - if [ -L "${SERVER_FILES}/@${MODNAME}" ] - then - echo -e "${yellow}Warning: The mod symlink ${SERVER_FILES}/@${MODNAME} already exists!${default}" - fi - if grep -qP "\b${1}\b" "${WORKSHOP_CFG}" - then - echo "The mod with id ${1} is already in the workshop configuration." - return - fi - echo "Adding mod id ${1}" - echo "${1}:MODNAME:1" >> ${WORKSHOP_CFG} - dologin - ${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" +workshop_download_item "${release_client_appid}" "${1}" +quit - # Make sure the install succeeded - if [ ! -d "${workshopfolder}/${1}" ] - then - echo -e "${red}Mod installation failed: The mod directory ${workshopfolder}/${1} was not created!${default}" - echo "Installation failed! See above (You probably need to use a real Steam login)" - # The mod is added temporarily into the workshop config. Since the installation failed, reemove it instead of updating it. - head -n-1 "${WORKSHOP_CFG}" > /tmp/workshop.cfg.tmp - mv /tmp/workshop.cfg.tmp "${WORKSHOP_CFG}" - return - fi - # Get the name of the newly added mod - MODNAME=$(get_mod_name ${1}) - symlink 1 ${1} "${MODNAME}" - # Lower case all the files in mod directories. - find "${workshopfolder}/${1}" -depth -exec rename -f 's/(.*)\/([^\/]*)/$1\/\L$2/' {} \; - # Copy the key files - copy_keys 1 ${1} - # Set the mod name in the workshop config file, as we don't know this at the start. - sed -i "${WORKSHOP_CFG}" -e "s/${1}:MODNAME/${1}:${MODNAME}/" - echo -e "Mod id ${1} - ${green}${MODNAME}${default} - added" - checkTypesXML ${1} install - checkInstall ${1} install -} - -# Remove a mod -remove(){ - checkTypesXML ${1} uninstall - checkInstall ${1} uninstall - if [ -d "${workshopfolder}/${1}" ] - then - MODNAME=$(get_mod_name ${1}) - echo "Removing directory ${workshopfolder}/${1}" - rm -rf "${workshopfolder}/${1}" - fi - if [ -L "${SERVER_FILES}/@${MODNAME}" ] - then - echo "Removing symlink ${SERVER_FILES}/@${MODNAME}" - rm -f "${SERVER_FILES}/@${MODNAME}" - fi - if grep -q ${1} "${WORKSHOP_CFG}" - then - echo "Removing workshop file entry" - sed -i "${WORKSHOP_CFG}" -e "/${1}:/d" - fi - echo -e "Mod id ${1} - ${red}${MODNAME}${default} - removed" -} - -# Activate / Deactivate a mod -activate(){ - W=${1} - shift - WW="" - if [[ ${W} = 0 ]] - then - WW="de" - UU="un" - fi - get_mods - X=1 - # Loop over mod list - for i in "${workshopID[@]}" - do - ID=$(echo ${i} | cut -d: -f1) - NAME=$(echo ${i} | cut -d: -f2) - ACTIVE=$(echo ${i} | cut -d: -f3) - # Find mod by ID or index - if [[ ${ID} = ${1} ]] || [[ ${X} = ${1} ]] - then - # Toggle state or report nothing burger - if [[ "${ACTIVE}" != "${W}" ]] - then - sed -i "${WORKSHOP_CFG}" -e "s/${ID}:${NAME}:[0-1]/${ID}:${NAME}:${W}/" - symlink ${W} ${ID} "${NAME}" - copy_keys ${W} ${ID} - checkTypesXML ${ID} ${UU}install - checkInstall ${ID} ${UU}install - echo "Mod id ${ID} - ${WW}activated" - else - echo -e "Mod id ${ID} - ${green}${NAME}${default} - is already ${WW}active" - fi - fi - X=$((X+1)) - done - list -} - -# List mods -list(){ - get_mods - X=1 - spaces=" " - echo -e " ID Name Active URL Size" - echo "------------------------------------------------------------------------------------------------------------------------" - for i in "${workshopID[@]}" - do - ID=$(echo ${i} | cut -d: -f1) - NAME=$(echo ${i} | cut -d: -f2) - ACTIVE=$(echo ${i} | cut -d: -f3) - SIZE=$(du -sh ${SERVER_FILES}/steamapps/workshop/content/221100/${ID} | awk '{print $1}') - if [[ ${ACTIVE} = "1" ]] - then - C="${green}" - else - C="${red}" - fi - printf "${C}%.3d %s %.23s %s %s https://steamcommunity.com/sharedfiles/filedetails/?id=%s %s${default}\n" ${X} ${ID} "${NAME}" "${spaces:${#NAME}+1}" ${ACTIVE} ${ID} ${SIZE} - X=$((X+1)) - done -} - -# Copy mod keys -copy_keys(){ - if [[ ${1} = 1 ]] - then - echo "Copying key files..." - cp -v ${workshopfolder}/${2}/keys/* "${SERVER_FILES}/keys/" || \ - cp -v ${workshopfolder}/${2}/key/* "${SERVER_FILES}/keys/" # Because mod authors can't stick to one way of doing things... - fi -} - -# Symlink mods -symlink(){ - W=${1} - ID=${2} - NAME=${3} - # Symlink it - if [ ! -L "${SERVER_FILES}/@${NAME}" ] && [[ ${W} = 1 ]] - then - ln -sv ${workshopfolder}/${ID} "${SERVER_FILES}/@${NAME}" - elif [[ "${W}" = "0" ]] - then - rm -vf "${SERVER_FILES}/@${NAME}" - fi -} - -# Assemble the mod command line -mod_cmd(){ - get_mods - mod_command_line="" - for i in "${workshopID[@]}" - do - NAME=$(echo ${i} | cut -d: -f2) - ACTIVE=$(echo ${i} | cut -d: -f3) - if [[ ${ACTIVE} = "1" ]] - then - mod_command_line="${mod_command_line}@${NAME};" - fi - done - if [[ ${mod_command_line} != "" ]] - then - mod_command_line='-mod='${mod_command_line::-1} - fi -} - -checkTypesXML(){ - # See if this mod has a types.xml. If so, manage it. - for path in "${workshopfolder}/${1}/extras" "${workshopfolder}/${1}" "/files/mods/${1}" - do - if [ -f "${path}/types.xml" ] - then - echo -n "The mod id ${1} has a types.xml: ${path}/types.xml. " - if [[ ${2} = "install" ]] - then - echo "Merging to missions..." - else - echo "Removing contents from missions..." - fi - /files/mods/types.sh ${1} ${2} ${path}/types.xml - break - fi - done -} - -checkInstall(){ - # 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 - -} -# Our internal RCON -rcon(){ - exec /usr/local/py3rcon/py3rcon.py --gui ~/py3rcon.config.json -} - -# Display the status of everything -status(){ - INSTALLED="${NO}" - LOGGED_IN="${NO}" - RUNNING="${NO}" - get_mods - - # DayZ Server files installation - if [ -f "${SERVER_INSTALL_FILE}" ] - then - INSTALLED="${YES}" - fi - # Logged into Steam - if [ -f "${STEAM_LOGIN}" ] - then - LOGGED_IN="${YES}" - if grep -q anonymous "${STEAM_LOGIN}" - then - ANONYMOUS="${yellow}(as anonymous)${default}" - else - ANONYMOUS="${green}(not anonymous)${default}" - fi - fi - # Running or not - if pidof DayZServer > /dev/null - then - # Uptime - D=$(date +%s) - F=$(date +%s -r ${SERVER_PROFILE}/server_console.log) - DAYS=$(( (${D} - ${F}) / 86400 )) -# UPTIME=$(date --date="$(( ${D} - ${F} ))" +"${DAYS} days %H:%M:%S") - UPTIME="${DAYS} days "$(date -d@$(($(date +%s) - $(date +%s -r ${SERVER_PROFILE}/server_console.log))) -u +"%H hours %M minutes %S seconds") - - RUNNING="${YES}\nUptime: ${green}${UPTIME}${default}" - # Current parameters - RUNNING="${RUNNING}\nRunning Parameters: $(cat /tmp/parameters)\nRunning mod parameter: $(cat /tmp/mod_command_line)" - fi - mod_cmd - # Map name -# MAP=$(grep -E "template=" ${SERVER_CFG_DST} | grep -vE "^//") - # Number of mods plus the list denoting on or off - echo -e " -Logged in to Steam: ${LOGGED_IN} ${ANONYMOUS} -Server files installed: ${INSTALLED} -Mods: -" - - list - - echo -e " -Server running: ${RUNNING} -Working parameters: ${parameters} -Working mod parameter: ${mod_command_line}" - MAP=$(grep template ${SERVER_CFG_DST} | grep -v "^//" | cut -d= -f2 | cut -d\; -f1) - echo "Map: ${MAP}" -} - -backup(){ - cd "${SERVER_FILES}"/mpmissions - DATE=$(date +'%Y-%m-%d-%H-%M-%S') - for i in $(ls) - do - B="${BACKUP_DIR}/${DATE}/" - echo "Backing up ${i} to ${B}..." - mkdir -p ${B} - cp -a "${i}" "${B}" - done -} - -# Capture the first argument and shift it off so we can pass $@ to every function -C=${1} -shift || { - usage -} - -case "${C}" in - a|activate) - activate 1 "${@}" - ;; - add) - add "${@}" - ;; - b|backup) - backup "${@}" - ;; - c|config) - config "${@}" - ;; - d|deactivate) - activate 0 "${@}" - ;; - f|force) - force - ;; - i|install) - install "${@}" - ;; - l|list) - list "${@}" - ;; - login) - login "${@}" - ;; - m|modupdate) - modupdate "${@}" - ;; - n|rcon) - rcon "${@}" - ;; - r|remove) - remove "${@}" - ;; - restart) - restart "${@}" - ;; - start) - start "${@}" - ;; - s|status) - status "${@}" - ;; - stop) - stop "${@}" - ;; - u|update) - update "${@}" - ;; - *) - usage "$*" - ;; -esac diff --git a/files/dz b/files/dz deleted file mode 120000 index e2f5e73..0000000 --- a/files/dz +++ /dev/null @@ -1 +0,0 @@ -dayzserver \ No newline at end of file diff --git a/files/mods/1964490092/types.env b/files/mods/1964490092/types.env new file mode 100644 index 0000000..cc67d55 --- /dev/null +++ b/files/mods/1964490092/types.env @@ -0,0 +1 @@ +TYPES=types-v6.xml diff --git a/files/mods/1964490092/types.xml b/files/mods/1964490092/types.xml deleted file mode 120000 index b634c88..0000000 --- a/files/mods/1964490092/types.xml +++ /dev/null @@ -1 +0,0 @@ -//home/user/serverfiles/steamapps/workshop/content/221100/1964490092/types-v6.xml \ No newline at end of file diff --git a/files/mods/2415195639/install.env b/files/mods/2415195639/install.env old mode 100755 new mode 100644 diff --git a/files/mods/2443122116/type.env b/files/mods/2443122116/type.env new file mode 100644 index 0000000..ea13dc9 --- /dev/null +++ b/files/mods/2443122116/type.env @@ -0,0 +1 @@ +xml_and_clasnames/snafu_types.xml diff --git a/files/mods/2443122116/types.xml b/files/mods/2443122116/types.xml deleted file mode 120000 index 1c75a2e..0000000 --- a/files/mods/2443122116/types.xml +++ /dev/null @@ -1 +0,0 @@ -/home/user/serverfiles/steamapps/workshop/content/221100/2443122116/xml_and_clasnames/snafu_types.xml \ No newline at end of file diff --git a/files/mods/2692979668/xml.env b/files/mods/2692979668/xml.env new file mode 100644 index 0000000..3a45bbe --- /dev/null +++ b/files/mods/2692979668/xml.env @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +CFGSPAWNABLETYPES=https://raw.githubusercontent.com/RedFalconKen/RedFalconFlightSystem-Heliz/main/Config%20Files/Event%20Spawn%20Config/RFFSHelis_cfgspawnabletypes.xml +#CFGEVENTSPAWNS=https://raw.githubusercontent.com/RedFalconKen/RedFalconFlightSystem-Heliz/main/Config%20Files/Event%20Spawn%20Config/Banov/RFFSHelis_cfgeventspawns.xml +CFGEVENTSPAWNS=https://raw.githubusercontent.com/RedFalconKen/RedFalconFlightSystem-Heliz/main/Config%20Files/Event%20Spawn%20Config/Chernarus/RFFSHelis_cfgeventspawns.xml +#CFGEVENTSPAWNS=https://raw.githubusercontent.com/RedFalconKen/RedFalconFlightSystem-Heliz/main/Config%20Files/Event%20Spawn%20Config/DeerIsle/RFFSHelis_cfgeventspawns.xml +#CFGEVENTSPAWNS=https://raw.githubusercontent.com/RedFalconKen/RedFalconFlightSystem-Heliz/main/Config%20Files/Event%20Spawn%20Config/Namalsk/RFFSHelis_cfgeventspawns.xml +EVENTS=https://raw.githubusercontent.com/RedFalconKen/RedFalconFlightSystem-Heliz/main/Config%20Files/Event%20Spawn%20Config/RFFSHelis_events.xml +TYPES=https://raw.githubusercontent.com/RedFalconKen/RedFalconFlightSystem-Heliz/main/Config%20Files/Types.XML/RFFSHelis_Types.xml diff --git a/files/mods/2878980498/types.env b/files/mods/2878980498/types.env new file mode 100644 index 0000000..a36947d --- /dev/null +++ b/files/mods/2878980498/types.env @@ -0,0 +1 @@ +TYPES=extras/types/rag_baseitems.xml diff --git a/files/mods/2878980498/types.xml b/files/mods/2878980498/types.xml deleted file mode 120000 index 0b36bda..0000000 --- a/files/mods/2878980498/types.xml +++ /dev/null @@ -1 +0,0 @@ -/home/user/serverfiles/steamapps/workshop/content/221100/2878980498/extras/types/rag_baseitems.xml \ No newline at end of file diff --git a/files/mods/@RaG_BaseItems b/files/mods/@RaG_BaseItems index 2059dd2..d0d3363 120000 --- a/files/mods/@RaG_BaseItems +++ b/files/mods/@RaG_BaseItems @@ -1 +1 @@ -@RaG_BaseItems \ No newline at end of file +2878980498 \ No newline at end of file diff --git a/files/mods/@RedFalconFlightSystemHeliz b/files/mods/@RedFalconFlightSystemHeliz new file mode 120000 index 0000000..04fa5f4 --- /dev/null +++ b/files/mods/@RedFalconFlightSystemHeliz @@ -0,0 +1 @@ +2692979668 \ No newline at end of file diff --git a/files/mods/types.sh b/files/mods/types.sh deleted file mode 100755 index d215193..0000000 --- a/files/mods/types.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env bash - -# A generic script to manage a mod's types.xml against all installed missions - -set -eE - -ID=${1} -MODE=${2} -TYPES_FILE="${workshopfolder}/${ID}/extras/types.xml" - -if [[ ${3} != "" ]] -then - TYPES_FILE="${3}" -fi - -for file in $(find ${SERVER_FILES}/mpmissions -name types.xml -print -prune) -do - if [[ ${MODE} = "uninstall" ]] - then - # Remove the lines that were added by the mod's extras/types.xml from - # every db/types.xml in all mission directories - - # Chop the top tag from the source file - tail -n+2 ${TYPES_FILE} > /tmp/types-tmp.xml - - # Chop the bottom tag from the source file - head -n-1 /tmp/types-tmp.xml > /tmp/types-src.xml - - # Remove that content from the original file - grep -qvxFf /tmp/types-src.xml ${file} - else - # Add the contents of extras/types.xml to every db/types.xml in all - # mission directories - xmllint --noout ${TYPES_FILE} 2> /dev/null && { - echo -e "${green}${TYPES_FILE} passes XML lint test!" - echo -e "Merging to $file...${default}" - # Chop the bottom tag from the destination file - head -n-1 ${file} > /tmp/types-dst.xml - - # Chop the top 2 tags, xml and types, from the source file - tail -n+2 ${TYPES_FILE} > /tmp/types-src.xml - - # Concatenate the two files back into the source file - cat /tmp/types-dst.xml /tmp/types-src.xml > /tmp/types.xml - - xmllint --noout /tmp/types.xml 2> /dev/null && { - cp -v /tmp/types.xml ${file} - } || { - # Try again, but chop the top 3 tags, hopefully xml and types, from the source file... - echo "First merge attempt failed, trying again..." - tail -n+3 ${TYPES_FILE} > /tmp/types-src.xml - - # Concatenate the two files back into the source file - cat /tmp/types-dst.xml /tmp/types-src.xml > /tmp/types.xml - - # And lint again. This should probably be a recursive function... - xmllint --noout /tmp/types.xml && { - cp -v /tmp/types.xml ${file} - } || { - echo "XML lint check after merge failed! No files changed!" - } - } - } || { - echo -e "${red}${TYPES_FILE} fails XML lint test!" - echo -e "This will have to be merged by hand!${default}" - } - fi -done diff --git a/files/serverDZ.cfg b/files/serverDZ.cfg index 4edfabb..3a19c82 100644 --- a/files/serverDZ.cfg +++ b/files/serverDZ.cfg @@ -7,7 +7,7 @@ maxPlayers = 60; // Maximum amount of players verifySignatures = 2; // Verifies .pbos against .bisign files. (only 2 is supported) -forceSameBuild = 1; // When enabled, the server will allow the connection only to clients with same the .exe revision as the server (value 0-1) +forceSameBuild = 0; // When enabled, the server will allow the connection only to clients with same the .exe revision as the server (value 0-1) disableVoN = 0; // Enable/disable voice over network (value 0-1) vonCodecQuality = 30; // Voice over network codec quality, the higher the better (values 0-30) @@ -85,8 +85,8 @@ class Missions { template="dayzOffline.chernarusplus"; // Chernarus // template="dayzOffline.enoch"; // Livonia -// template="empty.banov" // Banov -// template="empty.deerisle" // Deer Isle -// template="serverMission.Pripyat" // Pripyat +// template="empty.banov"; // Banov +// template="empty.deerisle"; // Deer Isle +// template="serverMission.Pripyat"; // Pripyat }; }; diff --git a/Dockerfile b/server/Dockerfile similarity index 69% rename from Dockerfile rename to server/Dockerfile index 3222987..c73832a 100644 --- a/Dockerfile +++ b/server/Dockerfile @@ -1,10 +1,7 @@ FROM debian:bullseye # Set debconf to run non-interactively and agree to the SteamCMD EULA -RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections \ - && echo steam steam/question select "I AGREE" | debconf-set-selections \ - && echo steam steam/license note '' | debconf-set-selections \ - && dpkg --add-architecture i386 +RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections # Add contrib and backports RUN sed -i /etc/apt/sources.list -e 's/main/main contrib non-free/' @@ -18,10 +15,6 @@ RUN apt-get update && apt-get -y upgrade && apt-get -y install --no-install-reco gdb \ git \ jq \ - lib32gcc-s1 \ - lib32stdc++6 \ - libcurl4:i386 \ - libsdl2-2.0-0:i386 \ libsdl2-2.0-0 \ libcap2 \ libxml2-utils \ @@ -29,9 +22,9 @@ RUN apt-get update && apt-get -y upgrade && apt-get -y install --no-install-reco nano \ procps \ python3-pip \ + strace \ wget \ - rename \ - steamcmd + xmlstarlet RUN update-alternatives --install /usr/bin/python python /usr/bin/python3.9 1 RUN update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1 @@ -42,18 +35,17 @@ ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en ENV LC_ALL en_US.UTF-8 +# Add our scripts directory to PATH +ENV PATH /files/bin:/server:${PATH} + # Add py3rcon RUN cd /usr/local && git clone https://github.com/indepth666/py3rcon.git -# Steamcmd needs its path added, as it ends up in /usr/games. -# Our server script is bind mounted in /files in docker-compose. -ENV PATH /usr/games:/files:${PATH} - # Setup a non-privileged user RUN groupadd user && \ useradd -l -g user user && \ - mkdir /home/user && \ - chown user:user /home/user + mkdir -p /home/user /serverfiles/mpmissions /mods /mpmissions /profiles && \ + chown -R user:user /home/user /serverfiles /mods /mpmissions /profiles # Use our non-privileged user USER user @@ -62,4 +54,4 @@ USER user WORKDIR /home/user # Run the server. -CMD ["dayzserver", "start"] +CMD ["start.sh"] diff --git a/server/bin/dz b/server/bin/dz new file mode 100755 index 0000000..06b8910 --- /dev/null +++ b/server/bin/dz @@ -0,0 +1,480 @@ +#!/usr/bin/env bash + +source dz-common + +# Server container base directories +SERVER_PROFILE="/profiles" +MPMISSIONS="${SERVER_FILES}/mpmissions" + +mkdir -p ${SERVER_PROFILE}/battleye + +# Server configuration file +SERVER_CFG_FILE="serverDZ.cfg" +SERVER_CFG_DST="${SERVER_PROFILE}/${SERVER_CFG_FILE}" +SERVER_CFG_SRC="${FILES}/${SERVER_CFG_FILE}" + +# Command line parameters except mod, as that is handled separately. +parameters="-config=${SERVER_CFG_DST} -port=${port} -freezecheck -BEpath=${SERVER_PROFILE}/battleye -profiles=${SERVER_PROFILE} -nologs" + +# Where mods are installed. +WORKSHOP_DIR="/mods/${release_client_appid}" +mod_command_line="" + +# Backups +BACKUP_DIR="${HOME}/backup" +if [ ! -d "${BACKUP_DIR}" ] +then + mkdir -p "${BACKUP_DIR}" +fi + +# Functions + +# Usage +usage(){ + echo -e " +${red}Bad option or arguments! ${yellow}${*}${default} + +Usage: ${green}$(basename $0)${yellow} option [ arg1 [ arg2 ] ] + +Options and arguments: + + a|activate id - Activate an installed DayZ Workshop items by id or index + b|backup - Backup the mission storage files in all mission directories + c|config - Update the internal serverDZ.cfg file from files/serverDZ.cfg on the host. Presents a unified diff if the internal file doesn't match the host file + d|deactivate id - Deactivate an installed DayZ Workshop items by id or index - Keeps the mod files but excludes it from the mod parameter + f|force - Forcibly kill the server. Use only as a last resort if the server won't shut down + l|list - List Workshop items and their details + n|rcon - Connect to the server using a python RCON client + r|restart - Restart the server without restarting the container + s|status - Shows the server's status: Running, uptime, mods, parameters, mod parameter, etc. + stop - Stop the server +${default}" + exit 1 +} + +loadconfig(){ + if [ ! -f "${SERVER_INSTALL_FILE}" ] + then + echo + echo -e "The DayZ server files are not installed. You need to do this first in the web UI." + echo + exit 1 + fi + # Handle the initial server configuration file + if [ ! -f ${SERVER_CFG_DST} ] + then + echo "Creating initial server configuration file" + cp "${SERVER_CFG_SRC}" "${SERVER_CFG_DST}" + fi + # battleye config and rconpassword setup + # The server creates a new file from this file, which it then uses. + # Let's make sure to delete it first + BE_SERVER_FILE="${SERVER_PROFILE}/battleye/beserver_x64.cfg" + ALT_BE_SERVER_FILE=$(find ${SERVER_PROFILE}/battleye -name "beserver_x64_active*") + if [ ! -f "${BE_SERVER_FILE}" ] && [ ! -f "${ALT_BE_SERVER_FILE}" ] + then + passwd=$(openssl rand -base64 8 | tr -dc 'A-Za-z0-9') + if [ "${passwd}" == "" ] + then + passwd=$(< /dev/urandom tr -dc 'A-Za-z0-9' | head -c10) + fi + if [ "${passwd}" == "" ] + then + printf "[ ${red}FAIL${default} ] Could not generate a passwort for RCON!\nOpen the Battleye config with 'dayzserver rcon'." + exit 1 + else + cat > "${BE_SERVER_FILE}" < ~/py3rcon.config.json +} + +get_mods(){ + workshoplist="" + mod_command_line="" + for link in $(ls -tdr ${SERVER_PROFILE}/@* 2> /dev/null) + do + ID=$(readlink ${link} | awk -F/ '{print $NF}') + MODNAME=$(get_mod_name ${ID}) + workshoplist+=" +workshop_download_item "${release_client_appid}" "${ID} + mod_command_line+="@${MODNAME};" + done + # Remove the trailing semi-colon. This is necessary. + if [[ ${mod_command_line} != "" ]] + then + mod_command_line="-mod=${mod_command_line::-1}" + fi +} + +# Make sure to clean up and report on exit, as these files remain in the container's volume +report() { + rm -f /tmp/mod_command_line /tmp/parameters + echo + echo -e "${yellow}========================================== error.log ==========================================" + find "${SERVER_PROFILE}" -name error.log -exec head {} \; -exec tail -n 30 {} \; -exec rm -f {} \; + echo + echo -e "========================================== script*.log ========================================" + find "${SERVER_PROFILE}" -name "script*.log" -exec head {} \; -exec tail -n 30 {} \; -exec rm -f {} \; + echo + echo -e "========================================== *.RPT ==============================================" + find "${SERVER_PROFILE}" -name "*.RPT" -exec ls -la {} \; -exec tail -n 30 {} \; -exec rm -f {} \; + echo + echo -e "========================================== End log ======================================${default}" +} + +mergexml(){ + # First copy the pristine files from upstream + echo "Copying pristine versions of cfgeconomycore.xml and cfgeventspawns.xml..." + find /mpmissions -name cfgeconomycore.xml -exec cp {} ${SERVER_FILES}{} \; +# find /mpmissions -name cfgeventspawns.xml -exec cp {} ${SERVER_FILES}{} \; + + # Follow https://community.bistudio.com/wiki/DayZ:Central_Economy_mission_files_modding and make a single XML + # file for the ones that can go into it. + for link in $(ls -tdr ${SERVER_PROFILE}/@* 2> /dev/null) + do + ID=$(readlink ${link} | awk -F/ '{print $NF}') + # Going to have to maintain a matrix of file names -> root node -> child node permutations + C="" + for i in "CFGSPAWNABLETYPES:spawnabletypes:type" "EVENTS:events:event" "TYPES:types:type" + do + var=$(echo ${i} | cut -d: -f1) + CHECK=$(echo ${i} | cut -d: -f2) + CHILD=$(echo ${i} | cut -d: -f3) + if [ -f "${WORKSHOP_DIR}/${ID}/${var,,}.xml" ] + then + FOUND=1 + echo "Adding ${WORKSHOP_DIR}/${ID}/${var,,}.xml to cfgeconomycore..." + for dir in $(ls ${MPMISSIONS}) + do + mkdir -pv ${MPMISSIONS}/${dir}/${ID} + cp -v ${WORKSHOP_DIR}/${ID}/${var,,}.xml ${MPMISSIONS}/${dir}/${ID}/${var,,}.xml + done + C+="-s / -t elem -n file -a /file -t attr -n name -v ${var,,}.xml -a /file -t attr -n type -v ${CHECK} -m /file /ce " + fi + done + if [[ ${C} != "" ]] + then + # Merge into every mpmissions file + find ${MPMISSIONS} -name cfgeconomycore.xml -exec \ + xmlstarlet ed -L -s / -t elem -n ce \ + -a /ce -t attr -n folder -v "${ID}" \ + ${C} \ + -m /ce /economycore {} \; + fi + # These still have to be merged into the upstream file + for i in "CFGEVENTSPAWNS:eventposdef:event" + do + var=$(echo ${i} | cut -d: -f1) + CHECK=$(echo ${i} | cut -d: -f2) + CHILD=$(echo ${i} | cut -d: -f3) + if [ -f "${WORKSHOP_DIR}/${ID}/${var,,}.xml" ] + then + echo "Merging ${var,,}.xml..." +# xmlstarlet ed -L -s / -t name event + fi + done + done +} + +# Start the server in the foreground +start(){ + # Do the report on exit. Set here so that it only happens once we're starting the server, and not for other actions. + trap ' + report + ' EXIT + get_mods + mergexml + cd ${SERVER_FILES} + # Run the server. Allow docker to restart the container if the script exits with a code other than 0. This is so we can + # safely shut the container down without killing the server within. + printf "[ ${green}DayZ${default} ] Server starting...\n" + # Save the mod command line and parameters that were used to start the server, so status reflects the running server's + # actual status with those + echo ${mod_command_line} > /tmp/mod_command_line + echo ${parameters} > /tmp/parameters + ./DayZServer "${mod_command_line}" ${parameters} + EXIT_CODE=$? + if [ -f ${SERVER_FILES}/restart ] + then + rm -f ${SERVER_FILES}/restart + EXIT_CODE=42 + fi + printf "\n[ ${yellow}DayZ${default} ] Server exited. Exit code: ${EXIT_CODE}\n" + exit ${EXIT_CODE} +} + +# Restarts the server by forcing an exit code other than 0, causing docker to restart the container. +restart(){ + touch "${SERVER_FILES}/restart" + echo "Restarting DayZ server..." + kill -TERM $(pidof DayZServer) +} + +# Stops the server cleanly and exits 0, which will stop the container. +stop(){ + echo "Stopping DayZ server..." + kill -TERM $(pidof DayZServer) +} + +# Forcibly kill the server, should it be necessary. +force(){ + echo "Forcibly stopping DayZ server..." + kill -KILL $(pidof DayZServer) +} + +# Handle any changes in the server config file by allowing them to be merged after viewing a diff. +config(){ + if ! diff -q "${SERVER_CFG_DST}" "${SERVER_CFG_SRC}" + then + echo "=========================================================================" + diff -Nau --color "${SERVER_CFG_DST}" "${SERVER_CFG_SRC}" | more + echo "=========================================================================" + if prompt_yn "The new server configuration file differs from what's installed. Use it?" + then + echo "Updating the server configuration file" + cp "${SERVER_CFG_SRC}" "${SERVER_CFG_DST}" + else + echo "NOT updating the server configuration file" + fi + else + echo "No differences found between ${SERVER_CFG_SRC} and ${SERVER_CFG_DST}" + fi +} + +get_mod_id_by_index(){ + X=1 + # Loop over mods + for link in $(ls -tdr ${SERVER_PROFILE}/@* 2> /dev/null) + do + ID=$(readlink ${link} | awk -F/ '{print $NF}') + if [[ ${X} = ${1} ]] + then + echo -n ${ID} + return + fi + X=$((X+1)) + done +} + +# Get mod name by ID or index +get_mod_name(){ + # Check for an ID + 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} +} + +# Activate / Deactivate a mod +activate(){ + W=${1} + shift + WW="" + COLOR="${green}" + if [[ ${W} = 0 ]] + then + WW="de" + UU="un" + COLOR="${red}" + fi + ID=$(get_mod_id_by_index2 ${1}) + MODNAME=$(get_mod_name ${ID}) + # Toggle state or report nothing burger + pushd "${SERVER_PROFILE}" > /dev/null + if [ -L "${SERVER_PROFILE}/@${MODNAME}" ] + then + rm -vf "${SERVER_PROFILE}/@${MODNAME}" + else + ln -s "${WORKSHOP_DIR}/${ID}" "${SERVER_PROFILE}/@${MODNAME}" +# echo -e "Mod id ${ID} - ${COLOR}${MODNAME}${default} - is already ${WW}active" + fi + echo -e "Mod id ${ID} - ${COLOR}${MODNAME}${default} ${WW}activated" + popd > /dev/null + status +} + +# Our internal RCON +rcon(){ + exec /usr/local/py3rcon/py3rcon.py --gui ~/py3rcon.config.json +} + +# List mods +activelist(){ + X=1 + C="${green}" + spaces=" " + have=no + for link in $(ls -tdr ${SERVER_PROFILE}/@* 2> /dev/null) + do + if [[ ${have} = "no" ]] + then + have="yes" + echo -e "\n ID Name URL Size" + echo "------------------------------------------------------------------------------------------------------------------------" + fi + ID=$(readlink ${link} | awk -F/ '{print $NF}') + MODNAME=$(get_mod_name ${ID}) + SIZE=$(du -sh "${WORKSHOP_DIR}/${ID}" | awk '{print $1}') + printf "${C}%.3d %s %.23s %s https://steamcommunity.com/sharedfiles/filedetails/?id=%s %s${default}\n" ${X} ${ID} "${MODNAME}" "${spaces:${#MODNAME}+1}" ${ID} ${SIZE} + X=$((X+1)) + done + echo +} + +# Display the status of everything +status(){ + loadconfig + INSTALLED="${NO}" + RUNNING="${NO}" + + # DayZ Server files installation + if [ -f "${SERVER_INSTALL_FILE}" ] + then + INSTALLED="${YES}" + fi + # Running or not + if pidof DayZServer > /dev/null + then + # Uptime + D=$(date +%s) + F=$(date +%s -r ${SERVER_PROFILE}/server_console.log) + DAYS=$(( (${D} - ${F}) / 86400 )) +# UPTIME=$(date --date="$(( ${D} - ${F} ))" +"${DAYS} days %H:%M:%S") + UPTIME="${DAYS} days "$(date -d@$(($(date +%s) - $(date +%s -r ${SERVER_PROFILE}/server_console.log))) -u +"%H hours %M minutes %S seconds") + + RUNNING="${YES}\nUptime: ${green}${UPTIME}${default}" + # Current parameters + RUNNING="${RUNNING}\nRunning Parameters: $(cat /tmp/parameters)\nRunning mod parameter: $(cat /tmp/mod_command_line)" + fi + + MAP="none" + # Map name + if [[ -f ${SERVER_CFG_DST} ]] + then + MAP=$(grep -E "template=" ${SERVER_CFG_DST} | grep -vE "^//") + fi + # Number of mods plus the list denoting on or off + echo -ne " +Server files installed: ${INSTALLED}" + if [[ "${INSTALLED}" = "${NO}" ]] + then + echo + echo + exit 0 + fi + get_mods + echo -ne " +Active mods: " + activelist + if [[ ${MODS} == "" ]] + then + echo -n "none" + fi + echo -e "${MODS} +Server running: ${RUNNING} +Working parameters: ${parameters} +Working mod parameter: ${mod_command_line}" + if [[ "${INSTALLED}" = "${YES}" ]] + then + MAP=$(grep template ${SERVER_CFG_DST} | grep -v "^//" | cut -d= -f2 | cut -d\; -f1) + echo "Map: ${MAP}" + fi + echo +} + +backup(){ + cd ${MPMISSIONS} + DATE=$(date +'%Y-%m-%d-%H-%M-%S') + for i in $(ls) + do + B="${BACKUP_DIR}/${DATE}/" + echo "Backing up ${i} to ${B}..." + mkdir -p ${B} + cp -a "${i}" "${B}" + done +} + +# Capture the first argument and shift it off so we can pass $@ to every function +C=${1} +shift || { + usage +} + +case "${C}" in + a|activate) + activate 1 "${@}" + ;; + add) + add "${@}" + ;; + b|backup) + backup "${@}" + ;; + c|config) + config "${@}" + ;; + d|deactivate) + activate 0 "${@}" + ;; + f|force) + force + ;; + i|install) + install "${@}" + ;; + l|list) + list "${@}" + ;; + login) + login "${@}" + ;; + m|modupdate) + modupdate "${@}" + ;; + n|rcon) + rcon "${@}" + ;; + r|remove) + remove "${@}" + ;; + r|restart) + restart "${@}" + ;; + start) + start "${@}" + ;; + s|status) + status "${@}" + ;; + stop) + stop "${@}" + ;; + u|update) + update "${@}" + ;; + *) + usage "$*" + ;; +esac diff --git a/server/start.sh b/server/start.sh new file mode 100755 index 0000000..33c05bb --- /dev/null +++ b/server/start.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +# Set PS1 so we know we're in the container +cat > .bashrc <> /etc/apt/sources.list + +# Install _only_ the necessary packages +RUN apt-get update && apt-get -y upgrade && apt-get -y install --no-install-recommends \ + curl \ + ca-certificates \ + gdb \ + git \ + gwenhywfar-tools \ + jq \ + lib32gcc-s1 \ + lib32stdc++6 \ + libcurl4:i386 \ + libsdl2-2.0-0:i386 \ + libsdl2-2.0-0 \ + libcap2 \ + libxml2-utils \ + locales \ + nano \ + procps \ + python3-pip \ + wget \ + rename \ + steamcmd \ + xmlstarlet + +RUN update-alternatives --install /usr/bin/python python /usr/bin/python3.9 1 +RUN update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1 + +# Set the locale +RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 + +# Steamcmd needs its path added, as it ends up in /usr/games. +# Our server script is bind mounted in /files in docker-compose. +ENV PATH /usr/games:/files/bin:/web:${PATH} + +# Install nodejs +RUN mkdir /usr/local/nvm +ENV NVM_DIR /usr/local/nvm +ENV NODE_VERSION 16.17.0 +RUN echo $NODE_VERSION + +# Install nvm with node and npm +RUN curl https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash \ + && . $NVM_DIR/nvm.sh \ + && nvm install $NODE_VERSION \ + && nvm alias default $NODE_VERSION \ + && nvm use default + +ENV NODE_PATH $NVM_DIR/versions/node/v$NODE_VERSION/lib/node_modules +ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH + +# Add py3rcon +RUN cd /usr/local && git clone https://github.com/indepth666/py3rcon.git + +# Setup a non-privileged user +RUN groupadd user && \ + useradd -l -g user user && \ + mkdir -p /home/user /serverfiles/mpmissions /serverfiles/steamapps/workshop/content /web && \ + chown -R user:user /home/user /serverfiles /web + +# Use our non-privileged user +USER user + +# The dayzserver script expects a home directory to itself. +WORKDIR /home/user + +# Run the web server +CMD ["start.sh"] diff --git a/web/bin/dz b/web/bin/dz new file mode 100755 index 0000000..9a4f962 --- /dev/null +++ b/web/bin/dz @@ -0,0 +1,350 @@ +#!/usr/bin/env bash + +source dz-common + +# An array to store Workshop items. Each element contains the mod's ID, name, and state (active or not). +WORKSHOP_DIR="/mods/${release_client_appid}" + +workshoplist="" + +# Functions + +# Usage +usage(){ + echo -e " +${red}Bad option or arguments! ${yellow}${*}${default} + +Usage: ${green}$(basename $0)${yellow} option [ arg1 [ arg2 ] ] + +Options and arguments: + + a|add id - Add a DayZ Workshop item by id. Added items become active by default + i|install - Install the DayZ server files + g|login - Login to Steam. + m|modupdate - Update the mod files + r|remove id - Remove all files and directories of a Workshop item by id + s|status - Shows Steam login status, if base files are installed, installed mods + u|update - Update the server files + x|xml id - Get and normalize XML files from a mod's template by id (Presumes template exists) +${default}" + exit 1 +} + +# "Manage" XML files. +xml(){ + /files/mods/xml.sh ${1} + mergexml ${1} +} + +# Copy mod keys +copy_keys(){ + if [[ ${1} = 1 ]] + then + echo "Copying key files..." + find ${WORKSHOP_DIR}/${2} -name "*.bikey" -exec cp -v {} "${SERVER_FILES}/keys/" \; + fi +} + +# Manage the mod symlink +symlink(){ + W=${1} + ID=${2} + NAME=${3} + if [ ! -L "${SERVER_FILES}/@${NAME}" ] && [[ ${W} = 1 ]] + then + ln -sv ${WORKSHOP_DIR}/${ID} "${SERVER_FILES}/@${NAME}" + elif [[ "${W}" = "0" ]] + then + rm -vf "${SERVER_FILES}/@${NAME}" + fi +} + +mergexml(){ + ID=${1} + # Going to have to maintain a matrix of file names -> root node -> child node permutations + for i in "CFGEVENTSPAWNS:eventposdef:event" "CFGSPAWNABLETYPES:spawnabletypes:type" "EVENTS:events:event" "TYPES:types:type" + do + var=$(echo ${i} | cut -d: -f1) + CHECK=$(echo ${i} | cut -d: -f2) + CHILD=$(echo ${i} | cut -d: -f3) + if [ -f "${WORKSHOP_DIR}/${ID}/${var,,}.xml" ] + then + echo "Normalizing ${WORKSHOP_DIR}/${ID}/${var,,}.xml..." + cp ${WORKSHOP_DIR}/${ID}/${var,,}.xml /tmp/x + if ! grep -q '<'${CHECK}'>' /tmp/x + then + echo " - has no root node <${CHECK}>. fixing..." + xmlstarlet ed -s / -t elem -n "${CHECK}" -m /${CHILD} /${CHECK} /tmp/x > /tmp/y + mv /tmp/y /tmp/x + fi + if ! grep -q ' /tmp/y + mv /tmp/y /tmp/x + fi + xmllint --noout /tmp/x || { + echo "The final file failed lint tests, aborting..." + exit 1 + } + # Keep the normalized version in the /mods directory, where they should have been from the start + cp /tmp/x ${WORKSHOP_DIR}/${ID}/${var,,}.xml + fi + done + exit 0 +} + +# Add a mod +add(){ + if [ -d "${WORKSHOP_DIR}/${1}" ] + then + echo -e "${yellow}Warning: The mod directory ${WORKSHOP_DIR}/${1} already exists!${default}" + MODNAME=$(get_mod_name ${1}) + fi + if [ -L "${SERVER_FILES}/@${MODNAME}" ] + then + echo -e "${yellow}Warning: The mod symlink ${SERVER_FILES}/@${MODNAME} already exists!${default}" + fi + echo "Adding mod id ${1}" + dologin + ${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" +workshop_download_item "${release_client_appid}" "${1}" +quit + # Make sure the install succeeded + if [ ! -d "${WORKSHOP_DIR}/${1}" ] + then + echo -e "${red}Mod installation failed: The mod directory ${WORKSHOP_DIR}/${1} was not created!${default}" + echo "Installation failed! See above (You probably need to use a real Steam login)" + return + fi + # Get the name of the newly added mod + MODNAME=$(get_mod_name ${1}) + symlink 1 ${1} "${MODNAME}" + # Lower case all the files in mod directories. + find "${WORKSHOP_DIR}/${1}" -depth -exec rename -f 's/(.*)\/([^\/]*)/$1\/\L$2/' {} \; + # Copy the key files + copy_keys 1 ${1} + echo -e "Mod id ${1} - ${green}${MODNAME}${default} - added" + mergexml ${ID} +# checkTypesXML ${1} install +# checkInstall ${1} install +} + +# Remove a mod +remove(){ +# checkTypesXML ${1} uninstall +# checkInstall ${1} uninstall + if [ -d "${WORKSHOP_DIR}/${1}" ] + then + MODNAME=$(get_mod_name ${1}) + echo "Removing directory ${WORKSHOP_DIR}/${1}" + rm -rf "${WORKSHOP_DIR}/${1}" + fi + if [ -L "${SERVER_FILES}/@${MODNAME}" ] + then + echo "Removing symlink ${SERVER_FILES}/@${MODNAME}" + rm -f "${SERVER_FILES}/@${MODNAME}" + fi + echo -e "Mod id ${1} - ${red}${MODNAME}${default} - removed" +} + +# Handle the Steam login information. +login(){ + if [ -f "${STEAM_LOGIN}" ] + then + if prompt_yn "The steam login is already set. Reset it?" + then + rm -f "${STEAM_LOGIN}" + else + echo "Not reset." + exit 0 + fi + fi + if [ ! -f "${STEAM_LOGIN}" ] + then + echo "Setting up Steam credentials" + echo -n "Steam Username (anonymous): " + read steamlogin + if [[ "${steamlogin}" = "" ]] + then + echo "Steam login set to 'anonymous'" + steamlogin="anonymous" + fi + echo "steamlogin=${steamlogin}" > "${STEAM_LOGIN}" + ${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" +quit + fi +} + +# "Perform" the Steam login. This just sources the file with the Steam login name. +dologin(){ + if [ -f "${STEAM_LOGIN}" ] + then + source "${STEAM_LOGIN}" + else + echo "No cached Steam credentials. Please configure this now: " + login + fi +} + +# Perform the installation of the server files. +install(){ + if [ ! -f "${SERVER_INSTALL_FILE}" ] || [[ ${1} = "force" ]] + then + printf "[ ${yellow}DayZ${default} ] Downloading DayZ Server-Files!\n" + dologin + ${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" +app_update "${release_server_appid}" validate +quit + else + printf "[ ${lightblue}DayZ${default} ] The server is already installed.\n" + fi +} + +# Update the server files. +update(){ + dologin + appmanifestfile=${SERVER_FILES}/steamapps/appmanifest_"${release_server_appid}".acf + printf "[ ... ] Checking for update:" + # gets currentbuild + currentbuild=$(grep buildid "${appmanifestfile}" | tr '[:blank:]"' ' ' | tr -s ' ' | cut -d \ -f3) + # Removes appinfo.vdf as a fix for not always getting up to date version info from SteamCMD + if [ -f "${HOME}/Steam/appcache/appinfo.vdf" ] + then + rm -f "${HOME}/Steam/appcache/appinfo.vdf" + fi + # check for new build + availablebuild=$(${STEAMCMD} +login "${steamlogin}" +app_info_update 1 +app_info_print "${release_server_appid}" +quit | \ + sed -n '/branch/,$p' | grep -m 1 buildid | tr -cd '[:digit:]') + if [ -z "${availablebuild}" ] + then + printf "\r[ ${red}FAIL${default} ] Checking for update:\n" + printf "\r[ ${red}FAIL${default} ] Checking for update:: Not returning version info\n" + exit + else + printf "\r[ ${green}OK${default} ] Checking for update:" + fi + # compare builds + if [ "${currentbuild}" != "${availablebuild}" ] || [[ ${1} = "force" ]] + then + printf "\r[ ${green}OK${default} ] Checking for update:: Update available\n" + printf "Update available:\n" + printf "\tCurrent build: ${red}${currentbuild}${default}\n" + printf "\tAvailable build: ${green}${availablebuild}${default}\n" + printf "\thttps://steamdb.info/app/${release_server_appid}/\n" + printf "\nApplying update" + # run update + dologin + ${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" +app_update "${release_server_appid}" validate +quit + modupdate + else + printf "\r[ ${green}OK${default} ] Checking for update:: No update available\n" + printf "\nNo update available:\n" + printf "\tCurrent version: ${green}${currentbuild}${default}\n" + printf "\tAvailable version: ${green}${availablebuild}${default}\n" + printf "\thttps://steamdb.info/app/${release_server_appid}/\n\n" + fi +} + +get_mods(){ + workshoplist="" + mod_command_line="" + for link in $(ls -tdr ${SERVER_FILES}/@* 2> /dev/null) + do + ID=$(readlink ${link} | cut -d/ -f7) + MODNAME=$(get_mod_name ${ID}) + workshoplist+=" +workshop_download_item "${release_client_appid}" "${ID} + mod_command_line+="@${MODNAME};" + done + if [[ ${mod_command_line} != "" ]] + then + mod_command_line='-mod='${mod_command_line::-1} + fi +} + + +# Update mods +modupdate(){ + echo "Updating mods..." + dologin + get_mods + ${STEAMCMD} +force_install_dir ${SERVER_FILES} +login "${steamlogin}" ${workshoplist} +quit + # Updated files come in with mixed cases. Fix that. + echo -ne "\nFixing file names..." + find "${WORKSHOP_DIR}" -depth -exec rename -f 's/(.*)\/([^\/]*)/$1\/\L$2/' {} \; + echo "done" + echo +} + +# Display the status of everything +status(){ + INSTALLED="${NO}" + LOGGED_IN="${NO}" + RUNNING="${NO}" + + # DayZ Server files installation + if [ -f "${SERVER_INSTALL_FILE}" ] + then + INSTALLED="${YES}" + fi + # Logged into Steam + if [ -f "${STEAM_LOGIN}" ] + then + LOGGED_IN="${YES}" + if grep -q anonymous "${STEAM_LOGIN}" + then + ANONYMOUS="${yellow}(as anonymous)${default}" + else + ANONYMOUS="${green}(not anonymous)${default}" + fi + fi + echo -ne " +Logged in to Steam: ${LOGGED_IN} ${ANONYMOUS} +Server files installed: ${INSTALLED}" + if [[ "${INSTALLED}" = "${NO}" ]] + then + echo + echo + exit 0 + fi + # Mods + echo -ne " +Mods: " + MODS=$(list) + if [[ ${MODS} == "" ]] + then + echo -n "none" + fi + echo -e "${MODS}" +} + +# Capture the first argument and shift it off so we can pass $@ to every function +C=${1} +shift || { + usage +} + +case "${C}" in + a|add) + add "${@}" + ;; + i|install) + install "${@}" + ;; + g|login) + login "${@}" + ;; + m|modupdate) + modupdate "${@}" + ;; + r|remove) + remove "${@}" + ;; + s|status) + status "${@}" + ;; + u|update) + update "${@}" + ;; + x|xml) + xml "${@}" + ;; + *) + usage "$*" + ;; +esac diff --git a/web/package-lock.json b/web/package-lock.json new file mode 100644 index 0000000..26ec4ed --- /dev/null +++ b/web/package-lock.json @@ -0,0 +1,1658 @@ +{ + "name": "web", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "web", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "express": "^4.18.2" + }, + "devDependencies": { + "nodemon": "^2.0.22" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nodemon": { + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", + "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dev": true, + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + } + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "nodemon": { + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", + "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", + "dev": true, + "requires": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dev": true, + "requires": { + "semver": "~7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "requires": { + "nopt": "~1.0.10" + } + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + } + } +} diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..f60d8be --- /dev/null +++ b/web/package.json @@ -0,0 +1,19 @@ +{ + "name": "web", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "express": "^4.18.2" + }, + "type": "module", + "devDependencies": { + "nodemon": "^2.0.22" + } +} diff --git a/web/root/index.css b/web/root/index.css new file mode 100644 index 0000000..dbee6cb --- /dev/null +++ b/web/root/index.css @@ -0,0 +1,38 @@ +body { + padding-top: 10px; + background-color: black; +} +.green { + color: green; + font-weight: bolder; +} +.yellow { + color: yellow; + font-weight: bolder; +} +.darkgrey { + background-color: darkgray; + font-weight: bolder; + margin-bottom: 10px; +} + +.modInfo { + background-color: aliceblue; +} + +.result { + padding: 5px; +} + +.selected { + background-color: cyan; +} + +.simulink { + cursor: pointer; + text-underline: blue; +} + +th, td { + padding-right: 10px +} diff --git a/web/root/index.html b/web/root/index.html new file mode 100644 index 0000000..9a882f7 --- /dev/null +++ b/web/root/index.html @@ -0,0 +1,21 @@ + + + + + DayZ Docker Server + + + + + + +
+ + + diff --git a/web/root/index.js b/web/root/index.js new file mode 100644 index 0000000..3c07c04 --- /dev/null +++ b/web/root/index.js @@ -0,0 +1,231 @@ +const template = ` +
+
+
+

DayZ Docker Server

+
+
+
+ +
+
+
+
+ Server files installed: {{ installed }} +
+
+ Version: {{ version }} +
+
+
+
+ {{ fetchError }} +
+
+
+

Mods

+ + + + + + +
Steam LinkMod Info
+
+
+ + + + + + + + + + + + + + + + + +
Steam LinkTitleSizeLast UpdatedSubscriptions
+ + + + {{ result.title }}{{ BKMG(result.file_size) }}{{ new Date(result.time_updated * 1000).toLocaleDateString("en-us") }}{{ result.lifetime_subscriptions }} + + +
+
+
+
+

{{ modInfo.name }} mod info:

+
+
+
+
+ ID: {{ modInfo.id }} +
+
+ Size: {{ modInfo.size.toLocaleString("en-US") }} +
+
+ Custom XML files: + +
+
+
+ +
+
+
+
+
+` + +export default { + name: 'DazDockerServer', + template: template, + data() { + return { + fetchError: "", + installed: false, + mods: [], + modInfo: "", + searchResults: [], + version: "Unknown", + XMLFile: "", + XMLInfo: "", + } + }, + methods: { + getModInfo(modId) { + fetch('/mod/' + modId) + .then(response => response.json()) + .then(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") + 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) + }) + }) + .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 + }) + }, + 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]) + } + }, + 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 + } + }) + .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 } + + */ diff --git a/web/start.sh b/web/start.sh new file mode 100755 index 0000000..02c9ab9 --- /dev/null +++ b/web/start.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# Set PS1 so we know we're in the container +if ! [ -f .bashrc ] +then + echo "Creating .bashrc..." + cat > .bashrc < { + let size = 0 + const files = fs.readdirSync(dirPath) + for (let i = 0; i < files.length; i++) { + const filePath = path.join(dirPath, files[i]) + const stats = fs.statSync(filePath) + if (stats.isFile()) { + size += stats.size + } else if (stats.isDirectory()) { + size += getDirSize(filePath) + } + } + return size +} + +const getCustomXML = (modId) => { + const ret = [] + for(const file of configFiles) { + if (fs.existsSync(config.modDir + d + modId + d + file)) { + ret.push({name:file}) + } + } + return ret +} + +const getModNameById = (id) => { + const files = fs.readdirSync(serverFiles, {encoding: 'utf8', withFileTypes: true}) + for (const file of files) { + if (file.isSymbolicLink()) { + const sym = fs.readlinkSync(serverFiles + d + file.name) + if(sym.indexOf(id) > -1) return file.name + } + } +} + +const getMods = () => { + const mods = [] + fs.readdirSync(config.modDir).forEach(file => { + const name = getModNameById(file) + mods.push({name:name,id:file}) + }) + return mods +} + +app.use(express.static('root')) + +// Get mod metadata by ID +app.get('/mod/:modId', (req, res) => { + const modId = req.params["modId"] + const modDir = config.modDir + d + modId + const customXML = getCustomXML(modId) + const ret = { + id: modId, + name: getModNameById(modId), + size: getDirSize(modDir), + customXML: customXML + } + res.send(ret) +}) + +// Get a mod's XML file +app.get('/mod/:modId/:file', (req, res) => { + const modId = req.params["modId"] + const file = req.params["file"] + const contents = fs.readFileSync(config.modDir + d + modId + d + file) + res.send(contents) +}) + +// Search for a mod +app.get(('/search/:searchString'), (req, res) => { + const searchString = req.params["searchString"] + const url = "https://api.steampowered.com/IPublishedFileService/QueryFiles/v1/?numperpage=1000&appid=221100&return_short_description=true&strip_description_bbcode=true&key=" + config.steamAPIKey + "&search_text=" + searchString + https.get(url, resp => { + let data = ''; + resp.on('data', chunk => { + data += chunk; + }); + resp.on('end', () => { + res.send(JSON.parse(data)) + }) + }).on('error', err => { + console.log(err.message) + }) +}) + +// 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") +}) + +/* + Get the status of things: + If the base files are installed, the version of the server, a list of mods, etc. + */ +app.get('/status', (req, res) => { + // FIXME Async/await this stuff... + const installed = fs.existsSync(config.installFile) + const mods = getMods() + const ret = { + "installed": installed, + "version": "1.20.bogus", + "mods": mods + } + res.send(ret) +}) + +app.listen(config.port, () => { + console.log(`Listening on port ${config.port}`) +})