mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-29 06:57:42 +01:00
Adds Dockerfile and Utility Scripts (#31446)
* Adds Dockerfile and Utility Scripts * Remove RUSTG_VERSION from _build_dependencies.sh * Moved documentation to references, added link to mkdocs.yml * Update references, finish documentation, fix typos * Fixed small error in docs and scripts * Add CI action to build and publish game server images * Bump CI action to compatible Ubuntu runner * Fix up the base image for NanoMap rendering * Remove commented out base for nanomap-build stage * Removed default values from Dockerfile build arguments Sanitized default arguments in Dockerfile Updated CI workflow to use _build_dependencies.sh to build the Docker image Added documentation on using --build-arg flags with the docker build command * Modify caching for Docker builds in CI * Add missing files to pacify Nanomap Renderer
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
# .dockerignore
|
||||
|
||||
# -------------------------
|
||||
# Git & VCS
|
||||
# -------------------------
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
# -------------------------
|
||||
# Editor / OS junk
|
||||
# -------------------------
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
# -------------------------
|
||||
# Build artifacts (BYOND)
|
||||
# -------------------------
|
||||
*.dmb
|
||||
*.rsc
|
||||
*.log
|
||||
|
||||
# -------------------------
|
||||
# TGUI build artifacts
|
||||
# (we rebuild these in Docker)
|
||||
# -------------------------
|
||||
tgui/node_modules
|
||||
tgui/public/tgui.bundle.js
|
||||
|
||||
# -------------------------
|
||||
# Rust build artifacts
|
||||
# -------------------------
|
||||
rust/target
|
||||
**/target
|
||||
|
||||
# -------------------------
|
||||
# Docker / CI artifacts
|
||||
# -------------------------
|
||||
docker-compose.yml
|
||||
docker-compose.*.yml
|
||||
|
||||
# -------------------------
|
||||
# SQL / runtime data
|
||||
# (keep schemas, drop local data)
|
||||
# -------------------------
|
||||
SQL/*.db
|
||||
SQL/*.sqlite
|
||||
|
||||
# -------------------------
|
||||
# Secrets (never bake these)
|
||||
# -------------------------
|
||||
secret/*
|
||||
!secret/.gitkeep
|
||||
|
||||
# -------------------------
|
||||
# Local tooling outputs
|
||||
# -------------------------
|
||||
SpacemanDMM.log
|
||||
odlint.log
|
||||
|
||||
# -------------------------
|
||||
# BYOND Windows-only binaries
|
||||
# (not used in Linux containers)
|
||||
# -------------------------
|
||||
dreamchecker.exe
|
||||
prof.dll
|
||||
rustlibs.dll
|
||||
rustlibs_prod.dll
|
||||
@@ -101,6 +101,67 @@ jobs:
|
||||
tools/ci/generate_maplist.sh
|
||||
DreamMaker -DMULTIINSTANCE -DCIMAP -DPARADISE_PRODUCTION_HARDWARE paradise.dme
|
||||
|
||||
docker_build:
|
||||
name: Build (Docker)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Project
|
||||
uses: actions/checkout@v6
|
||||
- name: Load Build Dependencies
|
||||
id: deps
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
source ./_build_dependencies.sh
|
||||
# export as step outputs
|
||||
{
|
||||
echo "NODE_VERSION=$NODE_VERSION"
|
||||
echo "RUST_VERSION=$RUST_VERSION"
|
||||
echo "STABLE_BYOND_MAJOR=$STABLE_BYOND_MAJOR"
|
||||
echo "STABLE_BYOND_MINOR=$STABLE_BYOND_MINOR"
|
||||
# metadata for provenance
|
||||
echo "VCS_REF=${GITHUB_SHA}"
|
||||
echo "BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
- name: Generate Docker Metadata
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/paradisess13/paradise
|
||||
tags: |
|
||||
# branch event
|
||||
type=ref,event=branch
|
||||
# pull request event
|
||||
type=ref,event=pr
|
||||
# full length sha
|
||||
type=sha,format=long
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Push Docker Image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
build-args: |
|
||||
NODE_VERSION=${{ steps.deps.outputs.NODE_VERSION }}
|
||||
RUST_VERSION=${{ steps.deps.outputs.RUST_VERSION }}
|
||||
STABLE_BYOND_MAJOR=${{ steps.deps.outputs.STABLE_BYOND_MAJOR }}
|
||||
STABLE_BYOND_MINOR=${{ steps.deps.outputs.STABLE_BYOND_MINOR }}
|
||||
VCS_REF=${{ steps.deps.outputs.VCS_REF }}
|
||||
BUILD_DATE=${{ steps.deps.outputs.BUILD_DATE }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
station_mapload_tests:
|
||||
name: Station Tests
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
@@ -89,3 +89,9 @@ _maps/map_files/**/*.mapmanipout.dmm
|
||||
# dmdoc and output
|
||||
dmdoc.exe
|
||||
dmdoc/
|
||||
|
||||
# don't share our secrets
|
||||
/secret
|
||||
|
||||
# don't share database dumps from docker containers
|
||||
/paradise_db.sql
|
||||
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
# Dockerfile
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
#
|
||||
# Welcome to the Dockerfile for Paradise Station crew, enjoy your stay!
|
||||
# For more info, please see: docs/references/docker.md
|
||||
#
|
||||
|
||||
# You MUST supply these to `docker build` with a `--build-arg` flag!
|
||||
ARG NODE_VERSION=0
|
||||
ARG RUST_VERSION=0
|
||||
ARG STABLE_BYOND_MAJOR=0
|
||||
ARG STABLE_BYOND_MINOR=0
|
||||
|
||||
# Gate: Were we supplied with all the required --build-arg flags?
|
||||
FROM ubuntu:24.04 AS build-dependencies
|
||||
# Required build arguments
|
||||
ARG NODE_VERSION
|
||||
ARG RUST_VERSION
|
||||
ARG STABLE_BYOND_MAJOR
|
||||
ARG STABLE_BYOND_MINOR
|
||||
# Optional build metadata
|
||||
ARG VCS_REF
|
||||
ARG BUILD_DATE
|
||||
# Verify and record all required arguments
|
||||
RUN [ "$NODE_VERSION" != "0" ] || { echo "\n\n--build-arg=NODE_VERSION=??? must be supplied\n\n"; exit 2; };
|
||||
RUN [ "$RUST_VERSION" != "0" ] || { echo "\n\n--build-arg=RUST_VERSION=??? must be supplied"; exit 2; };
|
||||
RUN [ "$STABLE_BYOND_MAJOR" != "0" ] || { echo "\n\n--build-arg=STABLE_BYOND_MAJOR=??? must be supplied"; exit 2; };
|
||||
RUN [ "$STABLE_BYOND_MINOR" != "0" ] || { echo "\n\n--build-arg=STABLE_BYOND_MINOR=??? must be supplied"; exit 2; };
|
||||
RUN { echo "# Generated at image build time. Do not edit."; \
|
||||
[ -n "${BUILD_DATE:-}" ] && echo "BUILD_DATE=$BUILD_DATE" || true; \
|
||||
[ -n "${VCS_REF:-}" ] && echo "VCS_REF=$VCS_REF" || true; \
|
||||
echo "NODE_VERSION=$NODE_VERSION"; \
|
||||
echo "RUST_VERSION=$RUST_VERSION"; \
|
||||
echo "STABLE_BYOND_MAJOR=$STABLE_BYOND_MAJOR"; \
|
||||
echo "STABLE_BYOND_MINOR=$STABLE_BYOND_MINOR"; \
|
||||
echo "BYOND_IMAGE=beestation/byond:${STABLE_BYOND_MAJOR}.${STABLE_BYOND_MINOR}"; \
|
||||
echo "RUST_IMAGE=rust:${RUST_VERSION}-slim-bookworm"; \
|
||||
} > /_built_as.env
|
||||
|
||||
# --- Mission Specifications Decrypted: Welcome to the Syndicate...
|
||||
|
||||
# BYOND Base Image
|
||||
FROM beestation/byond:${STABLE_BYOND_MAJOR}.${STABLE_BYOND_MINOR} AS base
|
||||
|
||||
# Build Rust Dependencies
|
||||
FROM rust:${RUST_VERSION}-slim-bookworm AS rust-build
|
||||
ARG RUST_VERSION
|
||||
RUN dpkg --add-architecture i386 \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
clang \
|
||||
lib32gcc-12-dev \
|
||||
mingw-w64 \
|
||||
mingw-w64-i686-dev \
|
||||
zlib1g-dev:i386 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN rustup target add i686-unknown-linux-gnu
|
||||
COPY rust rust
|
||||
WORKDIR /rust
|
||||
RUN cargo build --release --target "i686-unknown-linux-gnu"
|
||||
|
||||
# Build TGUI
|
||||
FROM base AS tgui-build
|
||||
ARG NODE_VERSION
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg \
|
||||
&& curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - \
|
||||
&& apt-get install -y --no-install-recommends nodejs \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
COPY tgui tgui
|
||||
RUN tgui/bin/tgui
|
||||
|
||||
# Render NanoMaps
|
||||
FROM ubuntu:24.04 AS nanomap-build
|
||||
COPY __odlint.dm __odlint.dm
|
||||
COPY __spacemandmm.dm __spacemandmm.dm
|
||||
COPY _maps _maps
|
||||
COPY code code
|
||||
COPY icons icons
|
||||
COPY interface interface
|
||||
COPY tools/github-actions tools/github-actions
|
||||
COPY paradise.dme paradise.dme
|
||||
RUN tools/github-actions/nanomap-renderer-invoker.sh
|
||||
|
||||
# Build paradise.dmb and paradise.rsc
|
||||
FROM base AS byond-build
|
||||
COPY . .
|
||||
COPY --from=nanomap-build /icons/_nanomaps icons/_nanomaps
|
||||
COPY --from=tgui-build /tgui/public tgui/public
|
||||
RUN DreamMaker paradise.dme
|
||||
|
||||
# Build final Paradise server image
|
||||
FROM base AS image
|
||||
COPY _build_dependencies.sh _build_dependencies.sh
|
||||
COPY _maps _maps
|
||||
COPY icons icons
|
||||
COPY strings strings
|
||||
COPY --from=build-dependencies /_built_as.env _built_as.env
|
||||
COPY --from=byond-build /paradise.dmb paradise.dmb
|
||||
COPY --from=byond-build /paradise.rsc paradise.rsc
|
||||
COPY --from=nanomap-build /icons/_nanomaps icons/_nanomaps
|
||||
COPY --from=rust-build /rust/target/i686-unknown-linux-gnu/release/librustlibs.so librustlibs.so
|
||||
COPY --from=tgui-build /tgui/public tgui/public
|
||||
VOLUME [ "/config", "/data" ]
|
||||
ENTRYPOINT [ "DreamDaemon", "paradise.dmb", "-port", "6666", "-trusted", "-close", "-verbose" ]
|
||||
EXPOSE 6666
|
||||
@@ -1,17 +1,17 @@
|
||||
# This file has all the information on what versions of libraries are thrown into the code
|
||||
# For dreamchecker
|
||||
export SPACEMANDMM_TAG=suite-1.11
|
||||
export SPACEMANDMM_TAG="suite-1.11"
|
||||
# For TGUI
|
||||
export NODE_VERSION=20
|
||||
export NODE_VERSION="20"
|
||||
# Stable Byond Major
|
||||
export STABLE_BYOND_MAJOR=516
|
||||
export STABLE_BYOND_MAJOR="516"
|
||||
# Stable Byond Minor
|
||||
export STABLE_BYOND_MINOR=1666
|
||||
export STABLE_BYOND_MINOR="1666"
|
||||
# Beta Byond Major - Uncomment and update if beta cycle active
|
||||
#export BETA_BYOND_MAJOR=516
|
||||
#export BETA_BYOND_MAJOR="516"
|
||||
# Beta Byond Minor - Uncomment and update if beta cycle active
|
||||
#export BETA_BYOND_MINOR=1657
|
||||
#export BETA_BYOND_MINOR="1657"
|
||||
# Python version for mapmerge and other tools
|
||||
export PYTHON_VERSION=3.13.5
|
||||
# RUSTG version
|
||||
export RUSTG_VERSION=v3.4.0-P
|
||||
export PYTHON_VERSION="3.13.5"
|
||||
# Rust version
|
||||
export RUST_VERSION="1.92"
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
# Set this to true if you are using database-based admins, or false if you want to define them in this file
|
||||
# If the database fails, this file will be used as fallback
|
||||
use_database_admins = false
|
||||
# Auto authenticate localhost users as admin? Useful for test servers, disable in producation
|
||||
# Auto authenticate localhost users as admin? Useful for test servers, disable in production
|
||||
enable_localhost_autoadmin = true
|
||||
# List of admin rank assignments and their permissions
|
||||
# These names ARE CASE SENSITIVE
|
||||
|
||||
@@ -0,0 +1,854 @@
|
||||
# Docker
|
||||
|
||||
Docker provides an alternative environment for developing, building, testing,
|
||||
and running software like Paradise. Keep reading to learn more.
|
||||
|
||||
## Quickstart
|
||||
|
||||
You just want to jump right into the thick of things? Awesome...
|
||||
|
||||
tools/docker/build
|
||||
tools/docker/init-db
|
||||
cp config/example/config.toml config/config.toml
|
||||
cat secret/db-ss13-password.txt
|
||||
nano config/config.toml
|
||||
# --- edit 4 fields in [database_configuration] ---
|
||||
# sql_enabled = true
|
||||
# sql_address = "paradise_db"
|
||||
# sql_username = "ss13"
|
||||
# sql_password = "<insert secret/db-ss13-password.txt here>"
|
||||
tools/docker/run
|
||||
# --- connect: byond://<your-ip>:6666/ ---
|
||||
|
||||
Welcome to the station crew, enjoy your stay.
|
||||
|
||||
## Helpful Background
|
||||
|
||||
In order to get the most benefit out of this, you should probably have a few
|
||||
things already set up:
|
||||
|
||||
- You should be able to run commands in a terminal window
|
||||
- You need Docker installed and permissions to run it
|
||||
|
||||
On many distros, you will need to add your user to the `docker` group, and
|
||||
perhaps login again in order to pick up the permissions change. Check the
|
||||
instructions for installing Docker on your platform to verify what is needed.
|
||||
|
||||
If you don't know this stuff yet, that's OK. Ask around if anybody has
|
||||
experience with Linux and/or Docker. Look up some tutorials online. Everybody
|
||||
starts somewhere, and this could be where you start.
|
||||
|
||||
### Motivation for Using Docker with Paradise
|
||||
|
||||
Paradise is a complex service with many moving parts. Database, NanoMaps, Rust
|
||||
libraries, TGUI, and the DreamMaker binary. The maintainers do a good job of
|
||||
providing pre-baked dependencies for most of these. Advanced developers need to
|
||||
learn how to compile Rust libraries and TGUI for themselves. This involves
|
||||
installing other software like Node.js and Rust, then learning the special
|
||||
commands to make things build. It is a complex task to learn everything that
|
||||
goes into it.
|
||||
|
||||
What if it could be simple? What if you could rebuild a Paradise server image
|
||||
from scratch with just one command?
|
||||
|
||||
tools/docker/build
|
||||
|
||||
Changed Rust libraries? Great, it'll recompile them. Changed TGUI stuff?
|
||||
Great, it'll recompile that too. Changed the code? Great, it'll regenerate
|
||||
the NanoMaps and build the game with DreamMaker. You'll create a new Docker
|
||||
image for Paradise, complete and ready to test immediately.
|
||||
|
||||
tools/docker/run
|
||||
|
||||
There is a little more to it in practice. If you want to actually play on your
|
||||
new server, you'll have to point BYOND at your machine. This means you need to
|
||||
discover the IP address of your Linux machine on your network (`hostname -I`)
|
||||
and feed it to BYOND like: `byond://192.168.0.xxx:6666/`. My network assigned
|
||||
my machine `192.168.0.104` but you'll need to find yours for yourself!
|
||||
|
||||
### Fantastic Scripts and Where To Run Them
|
||||
|
||||
This repository contains several scripts in the `tools/docker` folder. These
|
||||
scripts are intended as basic tools to get you set up, initialized, building,
|
||||
and running with a minimum of effort and hassle.
|
||||
|
||||
Some of the names `paradise`, `paradise_db`, `paradise_db_data`, `paradise_net`
|
||||
are baked into the scripts. If you want to run more than one database and more
|
||||
than one server on the same machine (and yes, this is possible!), you'll need
|
||||
to make some modifications to the scripts to make that happen.
|
||||
|
||||
In all cases, you should be running the scripts from the root of the
|
||||
repository. That is, if you unzipped the source code into a folder, or if you
|
||||
cloned the source repository from GitHub, you'll probably have a folder called
|
||||
`Paradise`. That's where you should be when you run the scripts.
|
||||
|
||||
You invoke a script like so:
|
||||
|
||||
tools/docker/build
|
||||
|
||||
Don't change into subdirectories or run the scripts from other locations.
|
||||
|
||||
Most of the scripts have some checks built in, to make sure things that need to
|
||||
be created have been created, and things that need to be running are running.
|
||||
The only truly dangerous script is:
|
||||
|
||||
tools/docker/zzz-destroy-everything-zzz
|
||||
|
||||
If you invoke that, you will delete everything these scripts have done. Any
|
||||
changes or other hard work you did will be permanently gone! Be careful!
|
||||
|
||||
## Docker Utility Scripts
|
||||
|
||||
There are eight utility scripts provided in the `tools/docker` folder. They
|
||||
are:
|
||||
|
||||
- `backup-db`: Create a backup file of a running database container
|
||||
- `build`: Build the `paradise:latest` Docker image
|
||||
- `debug-db`: Get a MariaDB shell into a running database container
|
||||
- `debug-server`: Create a server container, but run a bash shell instead of DreamDaemon
|
||||
- `init-db`: Create a `paradise_db` database container and initialize it
|
||||
- `restore-db`: Restore from backup file into a running database container
|
||||
- `run`: Create a server container and run DreamDaemon
|
||||
- `zzz-destroy-everything-zzz`: Delete all Paradise containers, images, networks, and volumes
|
||||
|
||||
## Creating a Local Paradise Server
|
||||
|
||||
This section will cover setting up a local Paradise server. You can use it to
|
||||
test code that you're developing, practice making maxcap bombs that you could
|
||||
never get away with on the real Paradise, or whatever you like.
|
||||
|
||||
### Building a Docker Image for Paradise
|
||||
|
||||
In order to create a Docker image, there must be a set of instructions for how
|
||||
that image should be created. This blueprint for making an image is called a
|
||||
`Dockerfile`. You can find the `Dockerfile` used to build the Paradise image
|
||||
in the root (top level) of the codebase.
|
||||
|
||||
Docker uses a multi-stage build. This builds up each thing (NanoMaps, Rust
|
||||
libraries, TGUI, the DreamMaker binary) in turn, then combines them all into a
|
||||
final image. This image will be used to create server containers when you're
|
||||
ready to run the software.
|
||||
|
||||
There is a script provided in the codebase to run this build command:
|
||||
|
||||
tools/docker/build
|
||||
|
||||
The first time you build, it may take several minutes to finish building.
|
||||
Future builds will not take nearly as long, because Docker will remember the
|
||||
intermediate steps in a cache, and re-use the result if nothing has changed
|
||||
since the last time.
|
||||
|
||||
After the command is done, you can see your new Docker image by issuing the
|
||||
command:
|
||||
|
||||
docker image ls
|
||||
|
||||
You should see something like this:
|
||||
|
||||
tux@linuxbox:~/Paradise$ docker image ls
|
||||
IMAGE ID DISK USAGE CONTENT SIZE
|
||||
paradise:latest bee29cf62073 398MB 0B
|
||||
|
||||
Note that our server image is named `paradise:latest`.
|
||||
|
||||
### Creating the Database Container
|
||||
|
||||
This is a script provided in the codebase to create and initialize a database
|
||||
container:
|
||||
|
||||
tools/docker/init-db
|
||||
|
||||
That will grab the MariaDB image from Docker Hub, and use it to create a new
|
||||
container with the MariaDB database software. It also does several other things
|
||||
so we'll go over those one by one.
|
||||
|
||||
Here you can run a command to see the container in action:
|
||||
|
||||
docker ps --all
|
||||
|
||||
You should see something like this:
|
||||
|
||||
tux@linuxbox:~/Paradise$ docker ps --all
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
4aa9b04f056b mariadb:12 "docker-entrypoint.s" 3 hours ago Up 3 seconds 3306/tcp paradise_db
|
||||
|
||||
Now we're ready to talk about the other things that `tools/docker/init-db` did
|
||||
behind the scenes.
|
||||
|
||||
#### Paradise Database Data Volume
|
||||
|
||||
Docker containers are ephemeral things; they are meant to be created and
|
||||
destroyed quite often, and not good or safe homes for things we want to keep.
|
||||
|
||||
A database stores information that we want to keep around for an indefinite
|
||||
period of time. So instead of allowing the database files to live in the
|
||||
database container, the `init-db` script will create a Docker volume named
|
||||
`paradise_db_data`. The volume is attached to a database container when that
|
||||
container is created.
|
||||
|
||||
You can see the new `paradise_db_data` volume with this command:
|
||||
|
||||
docker volume ls
|
||||
|
||||
You should see something like:
|
||||
|
||||
tux@linuxbox:~/Paradise$ docker volume ls
|
||||
DRIVER VOLUME NAME
|
||||
local paradise_db_data
|
||||
|
||||
If you want to know where those database files actually live on disk, you can
|
||||
use the following command:
|
||||
|
||||
docker volume inspect paradise_db_data
|
||||
|
||||
You should see something like:
|
||||
|
||||
tux@linuxbox:~/Paradise$ docker volume inspect paradise_db_data
|
||||
[
|
||||
{
|
||||
"CreatedAt": "2026-01-16T21:39:10-06:00",
|
||||
"Driver": "local",
|
||||
"Labels": null,
|
||||
"Mountpoint": "/home/tux/.local/share/docker/volumes/paradise_db_data/_data",
|
||||
"Name": "paradise_db_data",
|
||||
"Options": null,
|
||||
"Scope": "local"
|
||||
}
|
||||
]
|
||||
|
||||
Here we see `/home/tux/.local/share/docker/volumes/paradise_db_data/_data` is
|
||||
where the data actually lives on disk. Most of the time we don't really care.
|
||||
The data lives in the Docker volume, and that's usually all we need to know.
|
||||
|
||||
##### Database Credentials
|
||||
|
||||
When the `init-db` script creates a new database volume, it generates secure
|
||||
passwords for the newly created database. The MariaDB image includes some magic
|
||||
to automatically create a user and set passwords. This is done through some
|
||||
`MYSQL_*` environment variables; see the [Documentation for the MySQL Image](https://hub.docker.com/_/mysql#environment-variables).
|
||||
|
||||
MariaDB only applies the `MYSQL_*` environment variables the first time it
|
||||
initializes an empty data directory. You can create and destroy the database
|
||||
container as many times as you like. The passwords live in the database volume.
|
||||
|
||||
If you keep the `paradise_db_data` volume and recreate the container, the old
|
||||
passwords remain. If you delete the volume, the "data" part of the database is
|
||||
deleted. In that case `init-db` will create a new database volume, and generate
|
||||
new passwords for this new database.
|
||||
|
||||
The `init-db` script will output the passwords to the `secret` directory. The
|
||||
files are named:
|
||||
|
||||
secret/db-ss13-password.txt
|
||||
secret/db-root-password.txt
|
||||
|
||||
The game server container uses the `ss13` user and password.
|
||||
The utility scripts use the `root` user and password.
|
||||
|
||||
#### Paradise Network
|
||||
|
||||
One of the neat things about Docker is that it has an internal network concept.
|
||||
A Docker network indicates which containers are allowed to talk to one another.
|
||||
|
||||
This can be nice from an organizational standpoint. Instead of having to find
|
||||
IP addresses and individual ports for every container, they can live in their
|
||||
own network, and not interfere with one another.
|
||||
|
||||
This can also be nice from a security standpoint. The database container shows
|
||||
`3306/tcp`, but that means it's listening to port 3306 at the container on the
|
||||
network it is attached to. Only other containers on that same network can reach
|
||||
out to contact the database. By default it isn't exposed on the host, so
|
||||
`localhost:3306` won't work unless you explicitly publish the port.
|
||||
|
||||
The `init-db` script will create a Docker network called `paradise_net`. You
|
||||
can see the new network with the following command:
|
||||
|
||||
docker network ls
|
||||
|
||||
You should see something like:
|
||||
|
||||
tux@linuxbox:~/Paradise$ docker network ls
|
||||
NETWORK ID NAME DRIVER SCOPE
|
||||
760399a52cac bridge bridge local
|
||||
0987646c9a0b host host local
|
||||
f6aae2702134 none null local
|
||||
e06521af6993 paradise_net bridge local
|
||||
|
||||
Our scripts will create two containers on the `paradise_net` network; the
|
||||
database container, and the game server container. This means only containers
|
||||
attached to `paradise_net` can reach the database.
|
||||
|
||||
### Creating the Game Server Container
|
||||
|
||||
Now that the `init-db` script has done all the difficult setup work, we can
|
||||
create a container that runs the game server. That is, the container will be
|
||||
created using the `paradise:latest` image, run DreamDaemon, and make the game
|
||||
server available to the host network for connecting.
|
||||
|
||||
#### Configuration Work
|
||||
|
||||
Before we can create the game server container, it's important to talk about
|
||||
volume mounts. This is similar to the Docker volume that we talked about
|
||||
earlier, but is different because we are attaching specific folders from the
|
||||
file system to the game server container when we create it.
|
||||
|
||||
The Paradise game server needs two folders:
|
||||
|
||||
- `/config`: Configuration options are stored here
|
||||
- `/data`: Logs and current game mode are stored here
|
||||
|
||||
The `/data` folder does not need to exist. The script to create the game server
|
||||
container will create it, if it doesn't already exist.
|
||||
|
||||
The `/config` folder has some information that we need to adjust before our
|
||||
game server can start running.
|
||||
|
||||
##### Configuring config.toml
|
||||
|
||||
The main configuration file for a Paradise server is `config.toml`. You can
|
||||
find an example in the code repository at `config/example/config.toml`.
|
||||
|
||||
The first thing we want to do is make our own copy of the example, so we can
|
||||
modify it with our own configuration:
|
||||
|
||||
cp -v config/example/config.toml config/config.toml
|
||||
|
||||
You should see something like:
|
||||
|
||||
tux@linuxbox:~/Paradise$ cp -v config/example/config.toml config/config.toml
|
||||
'config/example/config.toml' -> 'config/config.toml'
|
||||
|
||||
Before we get started on editing, we need the password to the database. When
|
||||
`init-db` created the database container, it also generated a file to store
|
||||
the database password: `secret/db-ss13-password.txt`
|
||||
|
||||
We need to see this password so we can copy-pasta it into our configuration
|
||||
file. That is, tell the game server the secret password to talk to the
|
||||
database. Run this command:
|
||||
|
||||
cat secret/db-ss13-password.txt
|
||||
|
||||
You should see something like:
|
||||
|
||||
tux@linuxbox:~/Paradise$ cat secret/db-ss13-password.txt
|
||||
QFHvl6hRjRGAQsH8b45fhpgeilB11PcO
|
||||
|
||||
**NOTE**: This is a *secret*. You can write it down on a piece of paper for
|
||||
yourself, but you should NOT share it online, nor share any screenshots of
|
||||
the password.
|
||||
|
||||
Now we need a text editor to edit `config/config.toml`. Common text editors
|
||||
on Linux are `nano` and `vim`, but you can use your favorite. We'll assume you
|
||||
use `nano` and run a command like this:
|
||||
|
||||
nano config/config.toml
|
||||
|
||||
This will open the configuration file. If you scroll down, you'll find a
|
||||
section under the heading `[database_configuration]`. This is what we want
|
||||
to edit:
|
||||
|
||||
[database_configuration]
|
||||
# This section contains all the settings for the ingame database
|
||||
# If you are running in production, you will want to be using a database
|
||||
|
||||
# Enable/disable the database on a whole
|
||||
sql_enabled = false
|
||||
# SQL version. If this is a mismatch, round start will be delayed
|
||||
sql_version = 71
|
||||
# SQL server address. Can be an IP or DNS name
|
||||
sql_address = "127.0.0.1"
|
||||
# SQL server port
|
||||
sql_port = 3306
|
||||
# SQL server database name
|
||||
sql_database = "paradise_gamedb"
|
||||
# SQL server username
|
||||
sql_username = "root"
|
||||
# SQL server password
|
||||
sql_password = "please use something secure in a production environment"
|
||||
# Time in seconds for async queries to time out
|
||||
async_query_timeout = 10
|
||||
# How many threads is the async SQL engine allowed to open. 50 is normal. Trust me.
|
||||
async_thread_limit = 50
|
||||
|
||||
There are four fields that we need to change. The first field is
|
||||
`sql_enabled`. By default it is set to `false`. We want to change that to
|
||||
`true`:
|
||||
|
||||
sql_enabled = true
|
||||
|
||||
The second field we need to change is `sql_address`. This identifies the
|
||||
container that the game server will contact to reach the database. Our database
|
||||
container is called `paradise_db`, so that's what we'll change it to:
|
||||
|
||||
sql_address = "paradise_db"
|
||||
|
||||
The third field we need to change is `sql_username`. Our database uses a
|
||||
regular user called `ss13`. So we'll change the field as follows:
|
||||
|
||||
sql_username = "ss13"
|
||||
|
||||
The fourth field we need to change is `sql_password`. This is where we paste
|
||||
in that secret password that we printed out before.
|
||||
|
||||
sql_password = "QFHvl6hRjRGAQsH8b45fhpgeilB11PcO"
|
||||
|
||||
**NOTE**: This random password is just an example for this documentation. If
|
||||
you try to use the password written here, you won't be able to connect to your
|
||||
database. You'll need to see what the `init-db` script created for you. Run
|
||||
that command `cat secret/db-ss13-password.txt` and see what *your* password is.
|
||||
|
||||
When you are all done editing the file, save it. In `nano`, you type Ctrl-X,
|
||||
then Y for (Yes, I want to save my changes), then Enter. Other text editors
|
||||
will have their own command to save the file.
|
||||
|
||||
#### All Systems Go!
|
||||
|
||||
Now that we are fully configured, we are ready to create a game server
|
||||
container. To do so, run the following command:
|
||||
|
||||
tools/docker/run
|
||||
|
||||
Unless you edit the script, this will run the container in the foreground.
|
||||
You will see a long startup sequence, as DreamDaemon loads and initializes
|
||||
the Paradise software.
|
||||
|
||||
In another terminal window, you can see the game server container running
|
||||
with the following command:
|
||||
|
||||
docker ps --all
|
||||
|
||||
You should see something like:
|
||||
|
||||
tux@linuxbox:~/Paradise$ docker ps --all
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
62f9d7c37db7 paradise:latest "DreamDaemon paradis" 14 seconds ago Up 14 seconds 0.0.0.0:6666->6666/tcp, [::]:6666->6666/tcp paradise
|
||||
|
||||
While the database container had `3306/tcp`, we see that the game server
|
||||
container has a more complicated `0.0.0.0:6666->6666/tcp`. This means that
|
||||
the Docker host is forwarding it's own port 6666 to the game server container's
|
||||
port 6666.
|
||||
|
||||
That is to say, if somebody connects to the Linux machine that is running
|
||||
Docker on port 6666, then that network traffic will be sent to the game server
|
||||
container on port 6666. If you point BYOND's DreamSeeker at the Linux machine
|
||||
to port 6666, it will reach the running game server!
|
||||
|
||||
To stop the game server, you can press Ctrl+C in the terminal where you started
|
||||
the game server container. Or in another terminal window you can run the
|
||||
command:
|
||||
|
||||
docker stop paradise
|
||||
|
||||
You should see something like:
|
||||
|
||||
tux@linuxbox:~/Paradise$ docker stop paradise
|
||||
paradise
|
||||
|
||||
You can use `tools/docker/run` whenever you want to create and run a new game
|
||||
server container.
|
||||
|
||||
## Development with Docker
|
||||
|
||||
Development with Docker is pretty easy. Just edit your code in the repository,
|
||||
(any editor is fine; VS Code works well) and then run the build script again:
|
||||
|
||||
tools/docker/build
|
||||
|
||||
If the build is successful, you can start the server container and test your
|
||||
changes:
|
||||
|
||||
tools/docker/run
|
||||
|
||||
Do this repeatedly when you develop fun new features for the Paradise server.
|
||||
|
||||
## Advanced Database Administration
|
||||
|
||||
This section covers some of the more advanced tools for working with your
|
||||
database.
|
||||
|
||||
### Database Backups
|
||||
|
||||
Everybody knows that important data needs to be backed up. In fact, you should
|
||||
make it a habit to create a backup when you begin work. That way, no matter how
|
||||
many mistakes you make or how badly you break the database, you'll have a
|
||||
backup to undo all the mistakes and get back to the way things were when you
|
||||
got started.
|
||||
|
||||
To ask your database container to create a backup of your database, run the
|
||||
following command:
|
||||
|
||||
tools/docker/backup-db
|
||||
|
||||
You should see something like:
|
||||
|
||||
tux@linuxbox:~/Paradise$ tools/docker/backup-db
|
||||
Wrote backup: paradise_db.sql
|
||||
|
||||
Copy this `paradise_db.sql` file to a USB stick. Then, upload the
|
||||
`paradise_db.sql` file to Google Drive, OneDrive, Dropbox, or some other cloud
|
||||
service of your choice. If you do this, you'll have a 3-2-1 backup.
|
||||
|
||||
#### 3-2-1 Backups
|
||||
|
||||
The high-falutin' IT folks talk about 3-2-1 backups. You want at least THREE
|
||||
(3) copies of your data, on at least TWO (2) different kinds of media, and at
|
||||
least ONE (1) copy should be kept "off-site".
|
||||
|
||||
Here "off-site" means if the building where you regularly keep your data burns
|
||||
down, you know that you will still have a copy of your data that didn't get
|
||||
burned up in a fire.
|
||||
|
||||
When you generated the backup, you had two copies of your data; one in the
|
||||
Docker volume, and one in the `paradise_db.sql` file. When you copied it to a
|
||||
USB stick, you had three copies of your data, on two different kinds of media;
|
||||
your hard drive and an external USB stick. When you uploaded to the cloud,
|
||||
you had four copies, on three different kinds of media, and one is off-site;
|
||||
on somebody else's machine somewhere out there in the internet tubes.
|
||||
|
||||
This is as good of a backup as anybody in the IT world can give you.
|
||||
|
||||
### Database Restores
|
||||
|
||||
Remember that a backup is only good if A) You still have the backup when you
|
||||
need it, and B) You can restore the backup to create a working system!
|
||||
|
||||
We covered the making sure you still have a copy when you need it. In this
|
||||
section, we'll cover restoring the backup to create a new database container.
|
||||
|
||||
#### The Easy Database Restore Scenario
|
||||
|
||||
Scenario: You've got a running setup and you've been working on some code that
|
||||
requires database changes. After a bad run, you realize it messed up the data
|
||||
in the database, and you need some rows back for further testing.
|
||||
|
||||
This is an easy scenario. Your database container is still running. Your
|
||||
database volume is present and healthy. And you've got your backup file.
|
||||
|
||||
To restore our database from the backup file, just run this command:
|
||||
|
||||
tools/docker/restore-db
|
||||
|
||||
You should see something like:
|
||||
|
||||
tux@linuxbox:~/Paradise$ tools/docker/restore-db
|
||||
Restored backup: paradise_db.sql
|
||||
|
||||
And that's it! Your database is restored back to when the backup was created.
|
||||
That was easy, because this is the easy scenario.
|
||||
|
||||
#### The Hard Database Restore Scenario
|
||||
|
||||
Scenario: Your hard drive died. You had to replace the hard drive and download
|
||||
all the stuff again. Fortunately you had a copy of `config.toml` and
|
||||
`paradise_db.sql` up in the cloud. So you're ready to restore your system back
|
||||
to working condition.
|
||||
|
||||
First, we follow the directions to create a new database container and volume:
|
||||
|
||||
tools/docker/init-db
|
||||
|
||||
Next, we realize, this is a *brand new* database volume, which means it will
|
||||
have a *brand new* password. We need to see what that new password is:
|
||||
|
||||
cat secret/db-ss13-password.txt
|
||||
|
||||
Then we need to update `config/config.toml` and copy-pasta the new password
|
||||
in to the `sql_password` field:
|
||||
|
||||
nano config/config.toml
|
||||
|
||||
And now that we've got a running database container and our configuration file
|
||||
all straightened out, we'll restore from the backup:
|
||||
|
||||
tools/docker/restore-db
|
||||
|
||||
You should see something like:
|
||||
|
||||
tux@linuxbox:~/Paradise$ tools/docker/restore-db
|
||||
Restored backup: paradise_db.sql
|
||||
|
||||
And that's it! Your database is restored back to when the backup was created.
|
||||
That was harder, because that was the hard scenario where we needed to edit
|
||||
the password in the configuration file, because we lost our old one.
|
||||
|
||||
### Database Queries
|
||||
|
||||
Once you get to advanced development, you may need to make changes to the
|
||||
structure of the database. You may need to add or remove columns, or create
|
||||
a brand new table. And you'll need to query to see if your changes are working
|
||||
as you intended.
|
||||
|
||||
In order to get a shell into the database, run the following command:
|
||||
|
||||
tools/docker/debug-db
|
||||
|
||||
You should see something like:
|
||||
|
||||
tux@linuxbox:~/Paradise$ tools/docker/debug-db
|
||||
Reading table information for completion of table and column names
|
||||
You can turn off this feature to get a quicker startup with -A
|
||||
|
||||
Welcome to the MariaDB monitor. Commands end with ; or \g.
|
||||
Your MariaDB connection id is 4
|
||||
Server version: 12.1.2-MariaDB-ubu2404 mariadb.org binary distribution
|
||||
|
||||
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
|
||||
|
||||
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
|
||||
|
||||
MariaDB [paradise_gamedb]>
|
||||
|
||||
Here you are in a MariaDB shell and can execute a query like:
|
||||
|
||||
SELECT count(*) AS num_rounds FROM round;
|
||||
|
||||
You should see something like:
|
||||
|
||||
MariaDB [paradise_gamedb]> SELECT count(*) AS num_rounds FROM round;
|
||||
+------------+
|
||||
| num_rounds |
|
||||
+------------+
|
||||
| 2 |
|
||||
+------------+
|
||||
1 row in set (0.001 sec)
|
||||
|
||||
Teaching the ins and outs of writing and executing SQL queries is beyond the
|
||||
scope of this document. However, there are lots of resources online, and AI are
|
||||
surprisingly good tutors if you have questions about SQL databases and queries.
|
||||
|
||||
### Custom Database Container Creation
|
||||
|
||||
Earlier, we mentioned that the MariaDB image will use some environment
|
||||
variables to do special initialization when the database container is first
|
||||
created. This is not the only magic trick embedded in that MariaDB image.
|
||||
|
||||
Another useful feature is mounting some directory from the repository at
|
||||
`/docker-entrypoint-initdb.d` in the container. On the first run, when the
|
||||
database container is being created, all the SQL scripts found in that
|
||||
directory will be applied to the database in alphabetical order.
|
||||
|
||||
The script `tools/docker/init-db` mounts the `SQL` directory from the
|
||||
repository into the database container at `/docker-entrypoint-initdb.d`.
|
||||
By doing so, the SQL script that creates the Paradise database schema
|
||||
`paradise_schema.sql` is automatically applied when the volume is first
|
||||
created.
|
||||
|
||||
This is already incredibly useful, giving you a database with the correct
|
||||
tables and columns straight away. However, it can also be used to populate
|
||||
your database with extra data. Simply create a SQL script in the `SQL`
|
||||
directory before you create the database:
|
||||
|
||||
nano SQL/zzz_001_my_cool_data.sql
|
||||
|
||||
And you can put whatever queries you want to be run against the database when
|
||||
it is first created. For example, if you'd like to pre-populate your database
|
||||
with your favorite characters, give yourself and your friends admin rights, and
|
||||
that sort of thing; all of these are possible.
|
||||
|
||||
In order to leverage this, you will need to know how to write SQL queries, or
|
||||
at least find a SQL script that you want to apply to your database.
|
||||
|
||||
## Advanced Game Server Image Building and Debugging
|
||||
|
||||
### Customizing Game Server Image Builds
|
||||
|
||||
One of the magic powers hidden in the `tools/docker/build` script, is that
|
||||
additional arguments are supplied to the `docker build` command:
|
||||
|
||||
# build the docker image
|
||||
docker build "$@" \
|
||||
--build-arg "NODE_VERSION=${NODE_VERSION}" \
|
||||
--build-arg "RUST_VERSION=${RUST_VERSION}" \
|
||||
--build-arg "STABLE_BYOND_MAJOR=${STABLE_BYOND_MAJOR}" \
|
||||
--build-arg "STABLE_BYOND_MINOR=${STABLE_BYOND_MINOR}" \
|
||||
--tag "${SERVER_IMAGE}" \
|
||||
.
|
||||
|
||||
That little `"$@"` doesn't look like it's doing much, but if means you can
|
||||
supply any extra argument. Let's say you want Docker to skip it's internal
|
||||
cache and rebuild everything from absolute scratch:
|
||||
|
||||
tools/docker/build --no-cache
|
||||
|
||||
The `--no-cache` flag [disables the build cache](https://docs.docker.com/build/building/best-practices/#use---no-cache-for-clean-builds).
|
||||
You feed that flag to the utility script, and the `"$@"` passes it along to the
|
||||
`docker build` command that gets executed to build the game server image.
|
||||
|
||||
Maybe you don't like how often the NanoMaps are being rendered? You could add
|
||||
a flag `--build-arg "SKIP_NANOMAPS=TRUE"`. Now, if you modify the `Dockerfile`
|
||||
in the right way:
|
||||
|
||||
# Render NanoMaps
|
||||
FROM base AS nanomap-build
|
||||
ARG SKIP_NANOMAPS
|
||||
COPY _maps _maps
|
||||
COPY code code
|
||||
COPY icons icons
|
||||
COPY tools/github-actions tools/github-actions
|
||||
COPY paradise.dme paradise.dme
|
||||
RUN bash -lc '[ -n "${SKIP_NANOMAPS:-}" ] || tools/github-actions/nanomap-renderer-invoker.sh'
|
||||
|
||||
Now you can build and force it to skip rendering the NanoMaps:
|
||||
|
||||
tools/docker/build --build-arg "SKIP_NANOMAPS=TRUE"
|
||||
|
||||
Doing things like this are left as an exercise for the Expert Level reader.
|
||||
|
||||
### Debugging Game Server Images
|
||||
|
||||
If you modify the `Dockerfile` at some point, you'll end up changing what
|
||||
things end up in the final `paradise:latest` image. If things are missing, or
|
||||
out of place, or not what you expect, it can lead to strange errors.
|
||||
|
||||
If you ever wonder, "What all is in this image anyway?" then the command you
|
||||
want to run is:
|
||||
|
||||
tools/docker/debug-server
|
||||
|
||||
This will create a game server container. However, instead of running
|
||||
DreamDaemon, it will run a bash shell. You should see something like:
|
||||
|
||||
tux@linuxbox:~/Paradise$ tools/docker/debug-server
|
||||
root@28cac9c76c8a:/#
|
||||
|
||||
This is your opportunity to run shell commands like `ls -l` to see what the
|
||||
DreamDaemon software running in the container will see. For example:
|
||||
|
||||
root@28cac9c76c8a:/# ls -l
|
||||
total 184276
|
||||
drwxr-xr-x 3 root root 4096 Jan 4 16:56 _maps
|
||||
lrwxrwxrwx 1 root root 7 Sep 29 00:00 bin -> usr/bin
|
||||
drwxr-xr-x 2 root root 4096 Aug 24 16:05 boot
|
||||
drwxr-xr-x 2 root root 4096 Jan 17 07:47 config
|
||||
drwxr-xr-x 2 root root 4096 Jan 17 07:47 data
|
||||
drwxr-xr-x 5 root root 360 Jan 17 07:47 dev
|
||||
drwxr-xr-x 1 root root 4096 Jan 17 07:47 etc
|
||||
drwxr-xr-x 2 root root 4096 Aug 24 16:05 home
|
||||
drwxr-xr-x 1 root root 4096 Jan 4 16:56 icons
|
||||
lrwxrwxrwx 1 root root 7 Sep 29 00:00 lib -> usr/lib
|
||||
lrwxrwxrwx 1 root root 9 Sep 29 00:00 lib64 -> usr/lib64
|
||||
-rwxr-xr-x 1 root root 10426480 Jan 17 03:00 librustlibs.so
|
||||
drwxr-xr-x 2 root root 4096 Sep 29 00:00 media
|
||||
drwxr-xr-x 2 root root 4096 Sep 29 00:00 mnt
|
||||
drwxr-xr-x 2 root root 4096 Sep 29 00:00 opt
|
||||
-rw-r--r-- 1 root root 31268364 Jan 17 02:57 paradise.dmb
|
||||
-rw-r--r-- 1 root root 146923054 Jan 17 06:02 paradise.rsc
|
||||
dr-xr-xr-x 426 nobody nogroup 0 Jan 17 07:47 proc
|
||||
drwx------ 2 root root 4096 Sep 29 00:00 root
|
||||
drwxr-xr-x 3 root root 4096 Sep 29 00:00 run
|
||||
lrwxrwxrwx 1 root root 8 Sep 29 00:00 sbin -> usr/sbin
|
||||
drwxr-xr-x 2 root root 4096 Sep 29 00:00 srv
|
||||
drwxr-xr-x 3 root root 4096 Jan 4 16:56 strings
|
||||
dr-xr-xr-x 13 nobody nogroup 0 Jan 17 07:47 sys
|
||||
drwxr-xr-x 3 root root 4096 Jan 17 06:03 tgui
|
||||
drwxrwxrwt 2 root root 4096 Sep 29 00:00 tmp
|
||||
drwxr-xr-x 1 root root 4096 Sep 29 00:00 usr
|
||||
drwxr-xr-x 1 root root 4096 Sep 29 00:00 var
|
||||
|
||||
Wow! There's a whole Linux system in there, along with some Paradise stuff
|
||||
like `librustlibs.so`, `paradise.dmb`, and `paradise.rsc`. Have fun exploring!
|
||||
|
||||
## Uninstall Docker Changes
|
||||
|
||||
And now we've reached the end of the line. The last script we can talk about
|
||||
is `tools/docker/zzz-destroy-everything-zzz`. This script does what it says on
|
||||
the tin. It will destroy EVERYTHING created by the other scripts.
|
||||
|
||||
#
|
||||
# !!! This script will destroy everything !!!
|
||||
#
|
||||
# Your database and server containers will be stopped and deleted.
|
||||
# The database volume containing all the data in your database will be deleted.
|
||||
# The server image you last built will be deleted.
|
||||
# The docker network the database container and server container used to talk to each other will be deleted.
|
||||
# The entire secret/ directory, and the database credentials kept there, will be deleted.
|
||||
# The backup file for your database will be deleted.
|
||||
#
|
||||
# NOTE: Destroys all of the things. !! EVERYTHING !! You have been warned!
|
||||
#
|
||||
|
||||
It has an odd name `zzz-destroy-everything-zzz` and even refuses to run unless
|
||||
you provide the secret password:
|
||||
|
||||
tux@linuxbox:~/Paradise$ zzz-destroy-everything-zzz
|
||||
Error: If you *really* want to do this:
|
||||
|
||||
tools/docker/zzz-destroy-everything-zzz honk-honk-honk
|
||||
|
||||
There is NO way to UNDO this! If you do this, you destroy *everything*, PERMANENTLY!
|
||||
|
||||
Don't run it unless you're absolutely sure that you want that data to take a
|
||||
one-way trip to the bit bucket.
|
||||
|
||||
tools/docker/zzz-destroy-everything-zzz honk-honk-honk
|
||||
|
||||
If you followed my instructions about the 3-2-1 Backup, you can probably
|
||||
re-create it all in a few minutes. If you didn't save yourself a 3-2-1 Backup,
|
||||
then you really did destroy everything. 仕方がない。
|
||||
|
||||
## Expert Level
|
||||
|
||||
You've learned about all the utility scripts that have been provided to you.
|
||||
Note that all of the scripts follow the same basic pattern:
|
||||
|
||||
- Bind some bespoke names; `paradise_db`, `paradise:latest`, etc.
|
||||
- Run some basic sanity checks
|
||||
- Run a `docker $COMMAND` command, like `docker build` or `docker run`
|
||||
|
||||
The scripts use a common set of names between them, which is why they
|
||||
interoperate together so well. That is, you can run `tools/docker/backup-db`
|
||||
and later run `tools/docker/restore-db` because both scripts agree on what
|
||||
the database container is called, what the backup file is called, etc.
|
||||
|
||||
If you want to call your game server image `paradise:20260117`, there is
|
||||
nothing to stop you from editing the utilty script, or creating your own!
|
||||
|
||||
You will have to learn about bash shell script.
|
||||
You will have to learn about Docker commands.
|
||||
You might need to learn about SQL queries.
|
||||
|
||||
However, the utility scripts can guide you. Take a look at what docker command
|
||||
they are running under the hood, and what flags and names they give to the
|
||||
docker command.
|
||||
|
||||
You're a black belt now, so your real training can finally begin.
|
||||
|
||||
### First Lesson
|
||||
|
||||
The top of the `Dockerfile` (the blueprint for building a Docker image) has a
|
||||
list of required build arguments:
|
||||
|
||||
# You MUST supply these to `docker build` with a `--build-arg` flag!
|
||||
ARG NODE_VERSION=0
|
||||
ARG RUST_VERSION=0
|
||||
ARG STABLE_BYOND_MAJOR=0
|
||||
ARG STABLE_BYOND_MINOR=0
|
||||
|
||||
Per the comment, you'll need to supply these values to the `docker build`
|
||||
command with the `--build-arg` flag, similar to the way the tool script does.
|
||||
|
||||
# determine which versions to use in order to build everything
|
||||
source _build_dependencies.sh
|
||||
|
||||
# build the docker image
|
||||
docker build "$@" \
|
||||
--build-arg "NODE_VERSION=${NODE_VERSION}" \
|
||||
--build-arg "RUST_VERSION=${RUST_VERSION}" \
|
||||
--build-arg "STABLE_BYOND_MAJOR=${STABLE_BYOND_MAJOR}" \
|
||||
--build-arg "STABLE_BYOND_MINOR=${STABLE_BYOND_MINOR}" \
|
||||
--tag "${SERVER_IMAGE}" \
|
||||
.
|
||||
|
||||
Note that we `source _build_dependencies.sh` to pick up the recommended
|
||||
version numbers for the software used to build Paradise.
|
||||
|
||||
Let's say that you want to check if TGUI still works with a more modern
|
||||
version of Node.js. You modify `NODE_VERSION` in `_build_dependencies.sh` as
|
||||
follows:
|
||||
|
||||
# For TGUI
|
||||
export NODE_VERSION="24.13.0"
|
||||
|
||||
The tool script (`tools/docker/build`) will pick up that change, and build your
|
||||
Docker image using that version of Node. Will it actually build? I don't know.
|
||||
If not, you've got some software develoment work ahead of you. 頑張ってください!
|
||||
@@ -108,3 +108,4 @@ nav:
|
||||
- 'Movement Signals': './references/movement_signals.md'
|
||||
- 'Attack Chain': './references/attack_chain.md'
|
||||
- 'AI Controllers': './references/ai_controllers.md'
|
||||
- 'Docker': './references/docker.md'
|
||||
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
# backup-db
|
||||
# Create a backup dump of the database
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BACKUP_FILE="paradise_db.sql"
|
||||
DB_CONTAINER="paradise_db"
|
||||
MYSQL_DATABASE="paradise_gamedb"
|
||||
ROOT_PW_FILE="secret/db-root-password.txt"
|
||||
|
||||
# make sure we're at the root of the repository
|
||||
if [ ! -f "Dockerfile" ]; then
|
||||
echo >&2 "Error: No Dockerfile found. Are you at the repository root?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# make sure we're not clobbering an existing backup file
|
||||
if [ -f "${BACKUP_FILE}" ]; then
|
||||
echo >&2 "Error: Backup file ${BACKUP_FILE} already exists."
|
||||
echo >&2 "Rename or move it before creating a new backup."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# make sure the credentials we need to access the database exist
|
||||
if [ ! -f "${ROOT_PW_FILE}" ]; then
|
||||
echo >&2 "Error: missing ${ROOT_PW_FILE}. Run tools/docker/init-db first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# make sure the database is running, so we can back it up!
|
||||
if ! docker ps --format '{{.Names}}' | grep -qx "${DB_CONTAINER}"; then
|
||||
echo >&2 "Error: container '${DB_CONTAINER}' is not running."
|
||||
echo >&2 "Start it (or recreate it) before backing up."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# run mariadb-dump and create a backup dump of our database
|
||||
MYSQL_ROOT_PASSWORD=$(<"${ROOT_PW_FILE}")
|
||||
docker exec --interactive "${DB_CONTAINER}" \
|
||||
mariadb-dump --user root --password="${MYSQL_ROOT_PASSWORD}" "${MYSQL_DATABASE}" >"${BACKUP_FILE}"
|
||||
|
||||
# tell the user what we did
|
||||
echo "Wrote backup: ${BACKUP_FILE}"
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
# build
|
||||
# Build a Docker image for Paradise
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SERVER_IMAGE="paradise:latest"
|
||||
|
||||
# make sure we're at the root of the repository
|
||||
if [ ! -f "Dockerfile" ]; then
|
||||
echo >&2 "Error: No Dockerfile found. Are you at the repository root?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# determine which versions to use in order to build everything
|
||||
source _build_dependencies.sh
|
||||
|
||||
# build the docker image
|
||||
docker build "$@" \
|
||||
--build-arg "NODE_VERSION=${NODE_VERSION}" \
|
||||
--build-arg "RUST_VERSION=${RUST_VERSION}" \
|
||||
--build-arg "STABLE_BYOND_MAJOR=${STABLE_BYOND_MAJOR}" \
|
||||
--build-arg "STABLE_BYOND_MINOR=${STABLE_BYOND_MINOR}" \
|
||||
--tag "${SERVER_IMAGE}" \
|
||||
.
|
||||
|
||||
## uncomment to also tag the current git commit hash (short)
|
||||
# GIT_HASH_TAG="$(git rev-parse --short=12 HEAD)"
|
||||
# docker image tag paradise:latest paradise:${GIT_HASH_TAG}
|
||||
|
||||
## uncomment to also tag the current timestamp
|
||||
# ISO8601_TAG="$(date --utc +%Y%m%dT%H%M%SZ)"
|
||||
# docker image tag paradise:latest paradise:${ISO8601_TAG}
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
# debug-db
|
||||
# Connect to the database with a MariaDB shell
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BACKUP_FILE="paradise_db.sql"
|
||||
DB_CONTAINER="paradise_db"
|
||||
MYSQL_DATABASE="paradise_gamedb"
|
||||
ROOT_PW_FILE="secret/db-root-password.txt"
|
||||
|
||||
# make sure we're at the root of the repository
|
||||
if [ ! -f "Dockerfile" ]; then
|
||||
echo >&2 "Error: No Dockerfile found. Are you at the repository root?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# make sure the credentials we need to access the database exist
|
||||
if [ ! -f "${ROOT_PW_FILE}" ]; then
|
||||
echo >&2 "Error: missing ${ROOT_PW_FILE}. Run tools/docker/init-db first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# make sure the database is running, so we can back it up!
|
||||
if ! docker ps --format '{{.Names}}' | grep -qx "${DB_CONTAINER}"; then
|
||||
echo >&2 "Error: container '${DB_CONTAINER}' is not running."
|
||||
echo >&2 "Start it (or recreate it) before connecting with a shell."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# run a mariadb shell and connect to the database container
|
||||
MYSQL_ROOT_PASSWORD=$(<"${ROOT_PW_FILE}")
|
||||
docker exec --interactive --tty "${DB_CONTAINER}" \
|
||||
mariadb --password="${MYSQL_ROOT_PASSWORD}" "${MYSQL_DATABASE}"
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
# debug-server
|
||||
# Start a server container with a bash shell for debugging
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
NETWORK_NAME="paradise_net"
|
||||
SERVER_IMAGE="paradise:latest"
|
||||
|
||||
# make sure we're at the root of the repository
|
||||
if [ ! -f "Dockerfile" ]; then
|
||||
echo >&2 "Error: No Dockerfile found. Are you at the repository root?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# create a server container, but run a bash shell, not DreamDaemon
|
||||
docker run \
|
||||
--entrypoint /bin/bash \
|
||||
--interactive \
|
||||
--name paradise_debug_$(date +%s) \
|
||||
--network "${NETWORK_NAME}" \
|
||||
--rm \
|
||||
--tty \
|
||||
"${SERVER_IMAGE}"
|
||||
Executable
+88
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env bash
|
||||
# init-db
|
||||
# Initialize a MariaDB container to act as a database
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
DB_CONTAINER="paradise_db"
|
||||
DB_VOLUME="paradise_db_data"
|
||||
MYSQL_DATABASE="paradise_gamedb"
|
||||
MYSQL_USER="ss13"
|
||||
NETWORK_NAME="paradise_net"
|
||||
ROOT_PW_FILE="secret/db-root-password.txt"
|
||||
SS13_PW_FILE="secret/db-ss13-password.txt"
|
||||
|
||||
# make sure we're at the root of the repository
|
||||
if [ ! -f "Dockerfile" ]; then
|
||||
echo >&2 "Error: No Dockerfile found. Are you at the repository root?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# verify schema exists
|
||||
if [ ! -f "SQL/paradise_schema.sql" ]; then
|
||||
echo >&2 "Error: Unable to find SQL/paradise_schema.sql (run this from the repo root)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# make sure we've got a secrets directory to save things
|
||||
mkdir -p secret
|
||||
|
||||
# refuse to clobber an existing container
|
||||
if docker ps -a --format '{{.Names}}' | grep -qx "${DB_CONTAINER}"; then
|
||||
echo >&2 "Error: A container named '${DB_CONTAINER}' already exists."
|
||||
echo >&2 "If you meant to re-create it, run: docker rm -f ${DB_CONTAINER}"
|
||||
echo >&2 "Your data volume is: ${DB_VOLUME}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# if the data volume already exists
|
||||
if docker volume inspect "${DB_VOLUME}" >/dev/null 2>&1; then
|
||||
echo "Found existing volume '${DB_VOLUME}'. Reusing existing database volume and credentials."
|
||||
|
||||
# ensure credentials exist; if not, fail loudly (don't invent new ones)
|
||||
if [ ! -f "${ROOT_PW_FILE}" ] || [ ! -f "${SS13_PW_FILE}" ]; then
|
||||
echo >&2 "Error: Volume '${DB_VOLUME}' exists, but credential files are missing:"
|
||||
echo >&2 " ${ROOT_PW_FILE}"
|
||||
echo >&2 " ${SS13_PW_FILE}"
|
||||
echo >&2 "Without the original passwords, you must either:"
|
||||
echo >&2 " A) recover the password files from backups, or"
|
||||
echo >&2 " B) delete the volume to re-initialize (DATA LOSS): docker volume rm ${DB_VOLUME}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# read existing credentials
|
||||
MYSQL_ROOT_PASSWORD="$(<"${ROOT_PW_FILE}")"
|
||||
MYSQL_PASSWORD="$(<"${SS13_PW_FILE}")"
|
||||
|
||||
# since the data volume doesn't already exist
|
||||
else
|
||||
echo "No existing volume '${DB_VOLUME}'. Initializing a fresh database volume."
|
||||
|
||||
# create the persistent volume for MariaDB data
|
||||
docker volume create "${DB_VOLUME}" >/dev/null
|
||||
|
||||
# generate secure passwords (32 chars)
|
||||
MYSQL_PASSWORD="$(dd if=/dev/urandom bs=1 count=32 2>/dev/null | base64 | head -c 32)"
|
||||
MYSQL_ROOT_PASSWORD="$(dd if=/dev/urandom bs=1 count=32 2>/dev/null | base64 | head -c 32)"
|
||||
|
||||
# save credentials (overwrite intentionally on first init)
|
||||
echo "${MYSQL_ROOT_PASSWORD}" >secret/db-root-password.txt
|
||||
echo "${MYSQL_PASSWORD}" >secret/db-ss13-password.txt
|
||||
fi
|
||||
|
||||
# make sure the server can connect to this database via a docker network
|
||||
docker network create "${NETWORK_NAME}" >/dev/null 2>&1 || true
|
||||
|
||||
# create the container and import the Paradise database schema
|
||||
# --publish 3306:3306 \ <== add this below if you want to connect to the database from outside Docker
|
||||
docker run \
|
||||
--detach \
|
||||
--env MYSQL_DATABASE="${MYSQL_DATABASE}" \
|
||||
--env MYSQL_PASSWORD="${MYSQL_PASSWORD}" \
|
||||
--env MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD}" \
|
||||
--env MYSQL_USER="${MYSQL_USER}" \
|
||||
--mount type=bind,src="${PWD}/SQL",dst=/docker-entrypoint-initdb.d,readonly \
|
||||
--name "${DB_CONTAINER}" \
|
||||
--network "${NETWORK_NAME}" \
|
||||
--volume "${DB_VOLUME}:/var/lib/mysql" \
|
||||
mariadb:12
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
# restore-db
|
||||
# Restore a backup dump of the database
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BACKUP_FILE="paradise_db.sql"
|
||||
DB_CONTAINER="paradise_db"
|
||||
MYSQL_DATABASE="paradise_gamedb"
|
||||
ROOT_PW_FILE="secret/db-root-password.txt"
|
||||
|
||||
# make sure we're at the root of the repository
|
||||
if [ ! -f "Dockerfile" ]; then
|
||||
echo >&2 "Error: No Dockerfile found. Are you at the repository root?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# make sure the backup file exists!
|
||||
if [ ! -f "${BACKUP_FILE}" ]; then
|
||||
echo >&2 "Error: missing backup file ${BACKUP_FILE}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# make sure the credentials we need to access the database exist
|
||||
if [ ! -f "${ROOT_PW_FILE}" ]; then
|
||||
echo >&2 "Error: missing ${ROOT_PW_FILE}. Run tools/docker/init-db first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# make sure the database is running, so we can restore the backup
|
||||
if ! docker ps --format '{{.Names}}' | grep -qx "${DB_CONTAINER}"; then
|
||||
echo >&2 "Error: container '${DB_CONTAINER}' is not running."
|
||||
echo >&2 "Start it (or recreate it) before restoring."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# run mariadb and import the backup dump into our database container
|
||||
MYSQL_ROOT_PASSWORD=$(<"${ROOT_PW_FILE}")
|
||||
docker exec --interactive "${DB_CONTAINER}" \
|
||||
mariadb --user root --password="${MYSQL_ROOT_PASSWORD}" "${MYSQL_DATABASE}" <"${BACKUP_FILE}"
|
||||
|
||||
# tell the user what we did
|
||||
echo "Restored backup: ${BACKUP_FILE}"
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
# run
|
||||
# Create and run a BYOND server container for Paradise
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BYOND_PORT="6666"
|
||||
DB_CONTAINER="paradise_db"
|
||||
NETWORK_NAME="paradise_net"
|
||||
SERVER_CONTAINER="paradise"
|
||||
SERVER_IMAGE="paradise:latest"
|
||||
|
||||
# make sure we're at the root of the repository
|
||||
if [ ! -f "Dockerfile" ]; then
|
||||
echo >&2 "Error: No Dockerfile found. Are you at the repository root?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# make sure the server doesn't already exist
|
||||
if docker ps -a --format '{{.Names}}' | grep -qx "${SERVER_CONTAINER}"; then
|
||||
echo >&2 "Error: container '${SERVER_CONTAINER}' already exists."
|
||||
echo >&2 "Stop it with: docker rm -f ${SERVER_CONTAINER}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# make sure the database is running, so we can connect to it
|
||||
if ! docker ps --format '{{.Names}}' | grep -qx "${DB_CONTAINER}"; then
|
||||
echo >&2 "Error: container '${DB_CONTAINER}' is not running."
|
||||
echo >&2 "Start it (or recreate it) before running the server."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ensure the server has a 'config.toml' file to use
|
||||
[ -f "config/config.toml" ] || { echo >&2 "Error: missing config/config.toml (did you copy config/example/config.toml yet?)"; exit 1; }
|
||||
|
||||
# ensure we've got a data directory with 'mode.txt'
|
||||
mkdir -p data
|
||||
[ -f "data/mode.txt" ] || echo "extended" >data/mode.txt
|
||||
|
||||
# create and run the server container
|
||||
# --detach \ <== add this below if you want it to run in the background
|
||||
docker run \
|
||||
--init \
|
||||
--name "${SERVER_CONTAINER}" \
|
||||
--network "${NETWORK_NAME}" \
|
||||
--publish "${BYOND_PORT}:6666" \
|
||||
--rm \
|
||||
--mount type=bind,src="${PWD}/config",dst=/config,readonly \
|
||||
--mount type=bind,src="${PWD}/data",dst=/data \
|
||||
"${SERVER_IMAGE}"
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env bash
|
||||
# zzz-destroy-everything-zzz
|
||||
# Activate the Nuclear Authentication Disk
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
#
|
||||
# !!! This script will destroy everything !!!
|
||||
#
|
||||
# Your database and server containers will be stopped and deleted.
|
||||
# The database volume containing all the data in your database will be deleted.
|
||||
# The server image you last built will be deleted.
|
||||
# The docker network the database container and server container used to talk to each other will be deleted.
|
||||
# The entire secret/ directory, and the database credentials kept there, will be deleted.
|
||||
# The backup file for your database will be deleted.
|
||||
#
|
||||
# NOTE: Destroys all of the things. !! EVERYTHING !! You have been warned!
|
||||
#
|
||||
|
||||
BACKUP_FILE="paradise_db.sql"
|
||||
DB_CONTAINER="paradise_db"
|
||||
DB_VOLUME="paradise_db_data"
|
||||
NETWORK_NAME="paradise_net"
|
||||
SERVER_CONTAINER="paradise"
|
||||
SERVER_IMAGE="paradise:latest"
|
||||
ZZZ_DESTROY="honk-honk-honk"
|
||||
|
||||
# make sure we're at the root of the repository
|
||||
if [ ! -f "Dockerfile" ]; then
|
||||
echo >&2 "Error: No Dockerfile found. Are you at the repository root?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# make sure the user actually wants to run this...
|
||||
if [ "${1:-}" != "${ZZZ_DESTROY}" ]; then
|
||||
echo >&2 "Error: If you *really* want to do this:"
|
||||
echo >&2 ""
|
||||
echo >&2 "$0 ${ZZZ_DESTROY}"
|
||||
echo >&2 ""
|
||||
echo >&2 "There is NO way to UNDO this! If you do this, you destroy *everything*, PERMANENTLY!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# engaging nuclear authentication disk...
|
||||
docker stop "${SERVER_CONTAINER}" || true
|
||||
docker stop "${DB_CONTAINER}" || true
|
||||
docker rm "${DB_CONTAINER}" || true
|
||||
docker image rm "${SERVER_IMAGE}" || true
|
||||
docker network rm "${NETWORK_NAME}" || true
|
||||
docker volume rm "${DB_VOLUME}" || true
|
||||
rm -frv secret
|
||||
rm -fv "${BACKUP_FILE}"
|
||||
echo "You honked my battleship!"
|
||||
Reference in New Issue
Block a user