diff --git a/Dockerfile b/Dockerfile index 05546cf..e2640f9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1 +1,65 @@ -FROM alpine:3.12 \ No newline at end of file +FROM public.ecr.aws/docker/library/debian:12 + +ENV \ + LANG="en_US.UTF-8" \ + LC_ALL="en_US.UTF-8" + +USER root +WORKDIR /root + +# Install Base Packages +RUN apt update && apt upgrade -y \ + && dpkg --add-architecture i386 \ + && apt install -y \ + apt-transport-https \ + dirmngr \ + gnupg \ + ca-certificates \ + iproute2 \ + unzip \ + sqlite3 \ + fontconfig \ + lib32gcc-s1 \ + curl \ + wget \ + screen \ + sudo \ + youtube-dl \ + jq + +# Install Mono +RUN export GNUPGHOME=$(mktemp -d) \ + && gpg --recv-keys --no-default-keyring --keyring /etc/apt/trusted.gpg.d/mono-keyring.gpg --keyserver keyserver.ubuntu.com 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF \ + && echo "deb [signed-by=/etc/apt/trusted.gpg.d/mono-keyring.gpg] https://download.mono-project.com/repo/debian stable-buster main" > /etc/apt/sources.list.d/mono-official-stable.list \ + && wget https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb -O packages-microsoft-prod.deb \ + && dpkg -i packages-microsoft-prod.deb \ + && rm packages-microsoft-prod.deb \ + && apt-get update \ + && apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install -y mono-complete + +# Install Crystite +RUN echo 'deb [signed-by=/usr/share/keyrings/algiz.gpg] https://repo.algiz.nu/crystite bookworm main' | tee /etc/apt/sources.list.d/crystite.list \ + && mkdir -p /usr/share/keyrings \ + && wget -O /usr/share/keyrings/algiz.gpg https://repo.algiz.nu/algiz.gpg \ + && apt-get update \ + && apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install -y crystite + +# make data directories +RUN mkdir -p /data/resonite /data/steamcmd +VOLUME [ "/etc/crystite", "/data/resonite", "/data/steamcmd", "/data/home" ] + +# DEFAULT ENVS +ENV DISABLE_STEAMCMD=false +ENV USE_CRYSTITE=false +ENV CONFIG_FILE="/data/Config.json" +ENV COMMAND="/scripts/99_start.sh" +ENV RESONITE_ARGS="" +ENV RESONITE_MOD_LOADER=false +ENV MOD_URLS="" +ENV STOP_LAUNCH=false + +COPY crystite /mnt/crystite +COPY scripts /scripts +RUN chmod +x /scripts/* +ENTRYPOINT ["/scripts/00_setup.sh"] + diff --git a/crystite/appsettings.json b/crystite/appsettings.json new file mode 100644 index 0000000..2f7e4de --- /dev/null +++ b/crystite/appsettings.json @@ -0,0 +1,23 @@ +{ + "Serilog": { + "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.Debug" ], + "WriteTo": [ + { + "Name": "Console" + } + ], + "MinimumLevel": { + "Default": "Information", + "Override": { + "System": "Information", + "Microsoft": "Information", + "System.Net.Http.HttpClient": "Warning" + } + } + }, + "Headless": { + "resonitePath": "/data/resonite", + "assetCleanupInterval": "06:00:00", + "maxAssetAge": "07:00:00:00" + } +} \ No newline at end of file diff --git a/crystite/resonite.json b/crystite/resonite.json new file mode 100644 index 0000000..fdc3be3 --- /dev/null +++ b/crystite/resonite.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://raw.githubusercontent.com/Yellow-Dog-Man/JSONSchemas/main/schemas/HeadlessConfig.schema.json", + "comment": "This is the default Configuration file.", + "dataFolder": "/data/app/data", + "cacheFolder": "/data/app/cache", + "startWorlds": [ + { + "sessionName": "Docker Headless", + "hideFromPublicListing": false, + "isEnabled": true, + "maxUsers": 32, + "accessLevel": "Anyone", + "defaultFriendRole": "Builder", + "defaultAnonymousRole": "Builder", + "defaultVisitorRole": "Builder", + "loadWorldPresetName": "Grid" + } + ] +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a79aaf5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,61 @@ +services: + resonite: + # build: . + image: git.merith.xyz/oci/resonite:nightly + container_name: resonite + network_mode: host + stdin_open: true + tty: true + volumes: + # general data + - ./data:/data:rw + + # have volume entires in dockerfile, if removed this data will persist + # until the volume is removed + # - ./data/crystite/:/etc/crystite:rw + # - ./data/resonite:/data/resonite:rw + # - ./data/steamcmd:/data/steamcmd:rw + + # mount scripts for runtime, optional + - .build/scripts:/scripts:ro + - .build/crystite:/mnt/crystite:ro + + ## EDIT THESE ENVIRONMENT VARIABLES ## + environment: + # DO NOT REMOVE THIS VALUE + RUN_AS: 1000 + # THIS CONTROLS THE USER ID THAT THE SERVER RUNS AS + # This is specifically so *you* have control over the files] + + ## Options ## + ## These have their defualts set in the Dockerfile, and are safe to remove from this file + + # Prevents SteamCMD from updating the gamefiles + DISABLE_STEAMCMD: "false" + + # Wether to use crystite or not. + # if no STEAM_BETA key is provided, this is force set to true + USE_CRYSTITE: "false" + + # Where to located the config file, defaults to /data/Config.json + # if config file does not exist, it will generate a template one at that location + CONFIG_FILE: "/data/Config.json" + + # overrides the command used to start the server, defaults to /scripts/99_start.sh + COMMAND: "/scripts/99_start.sh" + + # Wether to enable the Resonite Mod Loader + # Crystite Support is expiremental at best. + RESONITE_MOD_LOADER: true + # list of mods to load, + MOD_URLS: | + https://github.com/New-Project-Final-Final-WIP/HeadlessTweaks/releases/latest/download/HeadlessTweaks.dll + https://github.com/Raidriar796/StresslessHeadless/releases/latest/download/StresslessHeadless.dll + + # Does not allow the server to start, helpful for debugging the container as a whole + STOP_LAUNCH: false + + env_file: + # Intended location for steam credentials + - ./steamcred.env + # STEAM_USER, STEAM_PASS, STEAM_AUTH, STEAM_BETA diff --git a/scripts/00_setup.sh b/scripts/00_setup.sh new file mode 100644 index 0000000..8a97a56 --- /dev/null +++ b/scripts/00_setup.sh @@ -0,0 +1,47 @@ +#!/bin/bash +## Set default env +if [ ! -n "$COMMAND" ]; then + COMMAND="/scripts/99_start.sh" +fi +if [ ! -n "$CONFIG_FILE" ]; then + CONFIG_FILE="/data/Config.json" +fi + +if [ ! -n "$RESONITE_ARGS" ]; then + RESONITE_ARGS="" +fi + +export DEFAULT_RESONITE_ARGS="-LogsPath /data/resonite/logs \ + -DataPath /data/app/data \ + -CachePath /data/app/cache \ + -HeadlessConfig $CONFIG_FILE \ + $RESONITE_ARGS" + +mkdir -p /data/home /data/resonite /data/steamcmd /etc/crystite/conf.d +## Have to do this here, as otherwise stuff doesnt work for some reason +# using source so runtime vars can be updated as needed +source /scripts/01_install.sh +source /scripts/02_setup_config.sh +source /scripts/03_download_mods.sh +if [ "$RUN_AS" != "" ]; then + echo "Running as $RUN_AS" + USER_ID=$(echo $RUN_AS | cut -d: -f1) + GROUP_ID=$(echo $RUN_AS | cut -d: -f2) + echo "User ID: $USER_ID" + echo "Group ID: $GROUP_ID" + + groupadd -g $GROUP_ID user + useradd -l -u $USER_ID -g $GROUP_ID user -d /home/user + + export HOME="/home/user" + chown $USER_ID:$GROUP_ID /data -R + chown $USER_ID:$GROUP_ID /etc/crystite -R + + echo "executing command: $COMMAND" + exec sudo -E -u user $COMMAND + +else + # If RUN_AS is not defined, execute the command as root + echo "executing command: $COMMAND" + exec $COMMAND +fi diff --git a/scripts/01_install.sh b/scripts/01_install.sh new file mode 100644 index 0000000..1378d29 --- /dev/null +++ b/scripts/01_install.sh @@ -0,0 +1,26 @@ +#!/bin/bash +cd /data/home || exit +if [ "$DISABLE_STEAMCMD" != "true" ]; then + echo "Running SteamCMD" + if [ ! -f "/data/steamcmd/steamcmd.sh" ]; then + cd /data/steamcmd || exit + curl -sqL "https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz" | tar zxvf - + fi + # if steam beta is empty, or matchs "beta_access_key" we don't need to dwonload the beta + if [ -z "$STEAM_BETA" ] || [ "$STEAM_BETA" == "beta_access_key" ]; then + echo "Downloading Resonite" + /data/steamcmd/steamcmd.sh +login anonymous +force_install_dir /data/resonite +app_update 2519830 +quit + else + echo "Downloading Resonite Headless" + /data/steamcmd/steamcmd.sh +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +force_install_dir /data/resonite +app_update 2519830 -beta headless -betapassword ${STEAM_BETA} validate +quit + fi +fi + +# if crystite is enabled, we don't need to check for resonite +if [ "$USE_CRYSTITE" != "true" ]; then + if [ ! -f "/data/resonite/Headless/Resonite.exe" ]; then + echo "Headless/Resonite.exe not found!" + echo "Forcing use of Crystite" + USE_CRYSTITE="true" + fi +fi \ No newline at end of file diff --git a/scripts/02_setup_config.sh b/scripts/02_setup_config.sh new file mode 100644 index 0000000..282b32d --- /dev/null +++ b/scripts/02_setup_config.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +## Setup Resonite Config +echo "Setting up Resonite Config" + +# if CONFIG_FILE is not set, use default config path +if [ ! -f $CONFIG_FILE ]; then + echo "No Resonite Config found, copying from template" + cp /mnt/crystite/resonite.json $CONFIG_FILE + # cat /mnt/crystite/resonite.json | jq '.' > $CONFIG_FILE +fi +echo "Generating Crystite configs" +if [ ! -f "/etc/crystite/appsettings.json" ]; then + cat /mnt/crystite/appsettings.json | jq '.' > /etc/crystite/appsettings.json +fi + +CONFIG_DATA=$(grep -v " null," "$CONFIG_FILE") +if [ ! -n "$CONFIG_DATA" ]; then + echo "Config file is empty, copying from template" + cp /mnt/crystite/resonite.json $CONFIG_FILE + CONFIG_DATA=$(grep -v " null," "$CONFIG_FILE") +fi +CONFIG_DATA=$(echo "$CONFIG_DATA" | jq ".comment = \"DO NOT EDIT: This file was automatically generated. Please edit $CONFIG_FILE instead.\"") +echo "{ \"Resonite\": $CONFIG_DATA }" > /etc/crystite/conf.d/_generated_resonite.json + +## Setup Modloader Configs +if [ "$RESONITE_MOD_LOADER" == "true" ]; then + echo "Setting up Modloader Configs" + echo "{\"Resonite\": {\"pluginAssemblies\": [\"/data/resonite/Headless/Libraries/ResoniteModLoader.dll\"]}}" | jq '.' > /etc/crystite/conf.d/_generated_rml.json + DEFAULT_RESONITE_ARGS=$(echo "$DEFAULT_RESONITE_ARGS -LoadAssembly /data/resonite/Headless/Libraries/ResoniteModLoader.dll") +else + echo "Modloader is disabled" + rm -f /etc/crystite/conf.d/_generated_rml.json +fi \ No newline at end of file diff --git a/scripts/03_download_mods.sh b/scripts/03_download_mods.sh new file mode 100644 index 0000000..7b26c2c --- /dev/null +++ b/scripts/03_download_mods.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +## TOOOTTALLY NOT AI GENERATED NOTHING TO SEE HERE +# real note, this was because I normally program in +# golang and really, *really* didnt want to have to write +# and maintain a binary in a language many people dont +# want to use simply because google made it... +# shell scripts are typically known by docker hosters so... + +# Define file URLs and their associated positions +file_urls=( + "https://github.com/resonite-modding-group/ResoniteModLoader/releases/latest/download/ResoniteModLoader.dll /data/resonite/Headless/Libraries/ResoniteModLoader.dll" + "https://github.com/resonite-modding-group/ResoniteModLoader/releases/latest/download/0Harmony.dll /data/resonite/Headless/rml_libs/0Harmony.dll" +) + +# Function to download a file from URL to destination +download_file() { + local url="$1" + local destination="$2" + # make sure destination directory exists + mkdir -p "$(dirname "$destination")" + # Use curl to download file + if curl -fsSL "$url" -o "$destination"; then + echo "File downloaded successfully: $destination" + else + echo "Failed to download file: $destination" + exit 1 + fi +} + +# Loop through each file URL and download +for file_url in "${file_urls[@]}"; do + read -r url destination <<< "$file_url" + # Backup existing file if exists + if [ -f "$destination" ]; then + mv "$destination" "${destination}.bak" + fi + # Download file + download_file "$url" "$destination" +done + +# Download additional files from a list of URLs to /data/resonite/Headless/rml_mods +# shellcheck disable=SC2153 +for url in $MOD_URLS ; do + destination="/data/resonite/Headless/rml_mods/$(basename "$url")" + # Check if file already exists, if yes, skip download + if [ ! -f "$destination" ]; then + echo "Downloading mod: $url" + download_file "$url" "$destination" + fi +done + +# if resonte mod loader is enabled, create and link rml_mods, libs, and config +if [ "$RESONITE_MOD_LOADER" == "true" ]; then + for dir in rml_mods rml_libs rml_config; do + if [ -d "/data/resonite/$dir" ]; then + continue + fi + mkdir -p "/data/resonite/Headless/$dir" + ln -s "/data/resonite/Headless/$dir" "/data/resonite/$dir" + done + if [ ! -f "/data/resonite/Libraries/ResoniteModLoader.dll" ]; then + mkdir -p "/data/resonite/Libraries" + ln -s "/data/resonite/Headless/Libraries/ResoniteModLoader.dll" "/data/resonite/Libraries/ResoniteModLoader.dll" + fi +fi \ No newline at end of file diff --git a/scripts/99_start.sh b/scripts/99_start.sh new file mode 100644 index 0000000..597347f --- /dev/null +++ b/scripts/99_start.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +if [ "$STOP_LAUNCH" == "true" ]; then + echo "STOP_LAUNCH is set to true, exiting..." + exit 0 +fi + +# Check if Crystite is enabled +if [ "$USE_CRYSTITE" == "true" ]; then + echo "Running Crystite" + echo "exec: /usr/lib/crystite/crystite" + /usr/lib/crystite/crystite +else + cd /data/resonite/Headless || exit + echo "Running Resonite" + echo "exec: mono Resonite.exe $DEFAULT_RESONITE_ARGS $RESONITE_ARGS" + mono Resonite.exe $DEFAULT_RESONITE_ARGS $RESONITE_ARGS +fi \ No newline at end of file diff --git a/steamcred.env b/steamcred.env new file mode 100644 index 0000000..920c08a --- /dev/null +++ b/steamcred.env @@ -0,0 +1,4 @@ +STEAM_USER=username +STEAM_PASS=password +STEAM_AUTH=auth_code +STEAM_BETA=beta_access_key \ No newline at end of file