From 2f6a9f818143f82a430201bc41903588d90d9ed8 Mon Sep 17 00:00:00 2001 From: FrozenFOXX Date: Mon, 25 May 2020 20:42:56 -0700 Subject: [PATCH] Added multistage, configs, and shrunk size --- Dockerfile | 55 ++++++--- Dockerfile.webtiles | 55 ++++++--- README.md | 16 ++- settings/init.txt | 87 ++++++++++++++ util/webtiles-init-player.sh | 15 +++ webserver/config.py | 212 +++++++++++++++++++++++++++++++++++ webserver/games.d/base.yaml | 127 +++++++++++++++++++++ 7 files changed, 538 insertions(+), 29 deletions(-) create mode 100644 settings/init.txt create mode 100644 webserver/config.py create mode 100644 webserver/games.d/base.yaml diff --git a/Dockerfile b/Dockerfile index a62dbbe..4189f9a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ -# Base image -FROM ubuntu:20.04 +# Builder image +FROM ubuntu:20.04 AS builder # Variables ENV CRAWL_REPO="https://github.com/crawl/crawl.git" \ @@ -11,34 +11,61 @@ ENV CRAWL_REPO="https://github.com/crawl/crawl.git" \ libsdl2-dev libfreetype6-dev libpng-dev ttf-dejavu-core advancecomp pngcrush" \ DEBIAN_FRONTEND=noninteractive -# Logic +# Install packages for the build RUN apt-get update && \ apt-get upgrade -y && \ apt-get install -y ${BUILD_DEPS} ${APP_DEPS} -# Install Tornado -RUN pip3 install tornado - # Retrieve crawl RUN git clone ${CRAWL_REPO} /src/ # Build crawl RUN cd /src/crawl-ref/source && \ - make -j4 USE_DGAMELAUNCH=y WEBTILES=y + make install -j4 DESTDIR=/app/ USE_DGAMELAUNCH=y WEBTILES=y -# Make directory for RCs -RUN mkdir -p /src/crawl-ref/source/rcs +# Set up webserver components +RUN cp -r /src/crawl-ref/source/webserver /app/ && \ + cp -r /src/crawl-ref/source/util /app/ -# Clean up unnecessary packages -RUN apt-get remove -y ${BUILD_DEPS} && \ - apt-get autoremove --purge -y && \ - rm -rf /var/lib/apt/lists/* +# Runtime image +FROM ubuntu:20.04 + +# Environment Variables +ENV APP_DEPS="bzip2 liblua5.1-0-dev python3-minimal python3-pip python3-yaml \ + python-is-python3 ncurses-term locales-all sqlite3 libpcre3 locales \ + lsof sudo libbot-basicbot-perl" \ + DATA_DIR=/data \ + DEBIAN_FRONTEND=noninteractive + +# Install packages for the runtime +RUN apt-get update && \ + apt-get upgrade -y && \ + apt-get install -y ${APP_DEPS} + +# Install Tornado +RUN pip3 install tornado + +# Copy over the compiled files +COPY --from=builder /app/ /app/ + +# Copy over custom configs +COPY settings/init.txt /app/settings/ +COPY util/webtiles-init-player.sh /app/util/ +COPY webserver/config.py /app/webserver/ +COPY webserver/games.d/* /app/webserver/games.d/ + +# Adjustments for the container +RUN mkdir -p /data/rcs && \ + mkdir -p /data/webserver + +# Clean up unnecessary package lists +RUN rm -rf /var/lib/apt/lists/* # Expose ports EXPOSE 8080 # Set the WORKDIR -WORKDIR /src/crawl-ref/source +WORKDIR /app # Launch WebTiles server CMD [ "./webserver/server.py" ] \ No newline at end of file diff --git a/Dockerfile.webtiles b/Dockerfile.webtiles index a62dbbe..4189f9a 100644 --- a/Dockerfile.webtiles +++ b/Dockerfile.webtiles @@ -1,5 +1,5 @@ -# Base image -FROM ubuntu:20.04 +# Builder image +FROM ubuntu:20.04 AS builder # Variables ENV CRAWL_REPO="https://github.com/crawl/crawl.git" \ @@ -11,34 +11,61 @@ ENV CRAWL_REPO="https://github.com/crawl/crawl.git" \ libsdl2-dev libfreetype6-dev libpng-dev ttf-dejavu-core advancecomp pngcrush" \ DEBIAN_FRONTEND=noninteractive -# Logic +# Install packages for the build RUN apt-get update && \ apt-get upgrade -y && \ apt-get install -y ${BUILD_DEPS} ${APP_DEPS} -# Install Tornado -RUN pip3 install tornado - # Retrieve crawl RUN git clone ${CRAWL_REPO} /src/ # Build crawl RUN cd /src/crawl-ref/source && \ - make -j4 USE_DGAMELAUNCH=y WEBTILES=y + make install -j4 DESTDIR=/app/ USE_DGAMELAUNCH=y WEBTILES=y -# Make directory for RCs -RUN mkdir -p /src/crawl-ref/source/rcs +# Set up webserver components +RUN cp -r /src/crawl-ref/source/webserver /app/ && \ + cp -r /src/crawl-ref/source/util /app/ -# Clean up unnecessary packages -RUN apt-get remove -y ${BUILD_DEPS} && \ - apt-get autoremove --purge -y && \ - rm -rf /var/lib/apt/lists/* +# Runtime image +FROM ubuntu:20.04 + +# Environment Variables +ENV APP_DEPS="bzip2 liblua5.1-0-dev python3-minimal python3-pip python3-yaml \ + python-is-python3 ncurses-term locales-all sqlite3 libpcre3 locales \ + lsof sudo libbot-basicbot-perl" \ + DATA_DIR=/data \ + DEBIAN_FRONTEND=noninteractive + +# Install packages for the runtime +RUN apt-get update && \ + apt-get upgrade -y && \ + apt-get install -y ${APP_DEPS} + +# Install Tornado +RUN pip3 install tornado + +# Copy over the compiled files +COPY --from=builder /app/ /app/ + +# Copy over custom configs +COPY settings/init.txt /app/settings/ +COPY util/webtiles-init-player.sh /app/util/ +COPY webserver/config.py /app/webserver/ +COPY webserver/games.d/* /app/webserver/games.d/ + +# Adjustments for the container +RUN mkdir -p /data/rcs && \ + mkdir -p /data/webserver + +# Clean up unnecessary package lists +RUN rm -rf /var/lib/apt/lists/* # Expose ports EXPOSE 8080 # Set the WORKDIR -WORKDIR /src/crawl-ref/source +WORKDIR /app # Launch WebTiles server CMD [ "./webserver/server.py" ] \ No newline at end of file diff --git a/README.md b/README.md index 3707b25..fa3a7cc 100644 --- a/README.md +++ b/README.md @@ -20,4 +20,18 @@ The following will run the latest Webtiles crawl server. ``` docker run -d --rm -p 8080:8080 --name=crawl_webtiles frozenfoxx/crawl:latest -``` \ No newline at end of file +``` + +## Persistent Deployment + +All player RCs and webserver database files are located within `/data` within the container. Bind mount a host directory to this location to maintain persistence. + +``` +docker run \ + -d \ + --rm \ + -v /data:/data \ + -p 8080:8080 \ + --name=crawl_webtiles \ + frozenfoxx/crawl:latest +``` diff --git a/settings/init.txt b/settings/init.txt new file mode 100644 index 0000000..c710efb --- /dev/null +++ b/settings/init.txt @@ -0,0 +1,87 @@ +##### Crawl Init file ############################################### +# For descriptions of all options, as well as some more in-depth information +# on setting them, consult the file +# options_guide.txt +# in your /docs directory. If you can't find it, the file is also available +# online at: +# https://github.com/crawl/crawl/blob/master/crawl-ref/docs/options_guide.txt +# +# Crawl uses the first file of the following list as its option file: +# * init.txt in the -rcdir directory (if specified) +# * .crawlrc in the -rcdir directory (if specified) +# * init.txt (in the Crawl directory) +# * ~/.crawl/init.txt (Unix only) +# * ~/.crawlrc (Unix only) +# * ~/init.txt (Unix only) +# * settings/init.txt (in the Crawl directory) + +##### Some basic explanation of option syntax ####################### +# Lines beginning with '#' are comments. The basic syntax is: +# +# field = value or field.subfield = value +# +# Only one specification is allowed per line. +# +# The terms are typically case-insensitive except in the fairly obvious +# cases (the character's name and specifying files or directories when +# on a system that has case-sensitive filenames). +# +# White space is stripped from the beginning and end of the line, as +# well as immediately before and after the '='. If the option allows +# multiple comma/semicolon-separated terms (such as +# autopickup_exceptions), all whitespace around the separator is also +# trimmed. All other whitespace is left intact. +# +# There are three broad types of Crawl options: true/false values (booleans), +# arbitrary values, and lists of values. The first two types use only the +# simple =, with later options - which includes your options that are different +# from the defaults - overriding earlier ones. List options allow using +=, ^=, +# -=, and = to append, prepend, remove, and reset, respectively. Usually you will +# want to use += to add to a list option. Lastly, there is := which you can use +# to create an alias, like so: +# ae := autopickup_exceptions +# From there on, 'ae' will be treated as if it you typed autopickup_exceptions, +# so you can save time typing it. +# + +##### Other files ################################################### +# You can include other files from your options file using the 'include' +# option. Crawl will treat it as if you copied the whole text of that file +# into your options file in that spot. You can uncomment some of the following +# lines by removing the beginning '#' to include some of the other files in +# this folder. + +# Some useful, more advanced options, implemented in LUA. +# include = advanced_optioneering.txt + +# Alternative vi bindings for Dvorak users. +# include = dvorak_command_keys.txt + +# Alternative vi bindings for Colemak users. +# include = colemak_command_keys.txt + +# Alternative vi bindings for Neo users. +# include = neo_command_keys.txt + +# Override the vi movement keys with a non-command. +# include = no_vi_command_keys.txt + +# Turn the shift-vi keys into safe move, instead of run. +# include = safe_move_shift.txt + +##### Ancient versions ############################################## +# If you're used to the interface of ancient versions of Crawl, you may +# get back parts of it by uncommenting the following options: + +# include = 034_command_keys.txt + +# And to revert monster glyph and colouring changes: + +# include = 052_monster_glyphs.txt +# include = 060_monster_glyphs.txt +# include = 071_monster_glyphs.txt +# include = 080_monster_glyphs.txt +# include = 0.9_monster_glyphs.txt +# include = 0.12_monster_glyphs.txt +# include = 0.13_monster_glyphs.txt +# include = 0.14_monster_glyphs.txt \ No newline at end of file diff --git a/util/webtiles-init-player.sh b/util/webtiles-init-player.sh index e69de29..31d245b 100755 --- a/util/webtiles-init-player.sh +++ b/util/webtiles-init-player.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +RCDIR=/data/rcs/ +INPROGRESSDIR=/data/rcs/running +TTYRECDIR=/data/rcs/ttyrecs/$1 +DEFAULT_RC=/app/settings/init.txt +PLAYERNAME=$1 + +mkdir -p $RCDIR +mkdir -p $INPROGRESSDIR +mkdir -p $TTYRECDIR + +if [ ! -f ${RCDIR}/${PLAYERNAME}.rc ]; then + cp ${DEFAULT_RC} ${RCDIR}/${PLAYERNAME}.rc +fi \ No newline at end of file diff --git a/webserver/config.py b/webserver/config.py new file mode 100644 index 0000000..6635a5c --- /dev/null +++ b/webserver/config.py @@ -0,0 +1,212 @@ +# Warning! Servers will not update or merge with the version controlled copy of +# this file, so any parameters here, and code that uses them, need to come +# without the assumption that they will be present in any given config.py on a +# server. Furthermore, on a typical rebuild in a production server, a running +# webtiles server *will not restart*, so you can't even assume that any config- +# specific code that you've added will be consistently present. This +# particularly impacts templated html files, which are loaded and called +# dynamically, so *do* get updated immediately on a rebuild. If something like +# client.html raises an exception, this will trigger 500 errors across the whole +# server. +# +# One useful workaround for all this is to get config paramters with the builtin +# `getattr` function: e.g. `getattr(config, "dgl_mode", False) will safely get +# this variable from the module, defaulting to False if it doesn't exist (and +# not raising an exception). `hasattr` is also safe. + +import logging +import os + +import yaml + +try: + from collections import OrderedDict +except ImportError: + from ordereddict import OrderedDict # type: ignore + + +dgl_mode = True + +bind_nonsecure = True # Set to false to only use SSL +bind_address = "" +bind_port = 8080 +# Or listen on multiple address/port pairs (overriding the above) with: +# bind_pairs = ( +# ("127.0.0.1", 8080), +# ("localhost", 8082), +# ("", 8180), # All addresses +# ) + +logging_config = { +# "filename": "webtiles.log", + "level": logging.INFO, + "format": "%(asctime)s %(levelname)s: %(message)s" +} + +password_db = "/data/webserver/passwd.db3" +# Uncomment and change if you want this db somewhere separate from the +# password_db location. +settings_db = "/data/webserver/user_settings.db3" + +static_path = "./webserver/static" +template_path = "./webserver/templates/" + +# Path for server-side unix sockets (to be used to communicate with crawl) +server_socket_path = None # Uses global temp dir + +# Server name, so far only used in the ttyrec metadata +server_id = "" + +# Disable caching of game data files +game_data_no_cache = True + +# Watch socket dirs for games not started by the server +watch_socket_dirs = False + +use_game_yaml = True + +# Game configs +# +# You can define game configs in two ways: +# 1. With a static dictionary `games` +# 2. As extra games to append to this list from `load_games.load_games` (which +# by default loads games as defined in `games.d/*.yaml`). +# +# All options in this config are documented in games.d/base.yaml. +games = OrderedDict([ + ("dcss-web-trunk", dict( + name = "Play trunk", + crawl_binary = "/app/bin/crawl", + rcfile_path = "/data/rcs/", + macro_path = "/data/rcs/", + morgue_path = "/data/rcs/%n", + inprogress_path = "/data/rcs/running", + ttyrec_path = "/data/rcs/ttyrecs/%n", + socket_path = "/data/rcs", + client_path = "/app/webserver/game_data/", + # dir_path = ".", + # cwd = ".", + morgue_url = None, + show_save_info = True, + milestone_path = "/data/rcs/milestones", + send_json_options = True, + # env = {"LANG": "en_US.UTF8"}, + )), +]) + + +dgl_status_file = "/data/rcs/status" + +# Extra paths to tail for milestone updates. This is a legacy setting, you +# should use `milestone_path` or `dir_path` for each game in the games dict. +# (This setting can be a string or list of strings.) +milestone_file = ["./milestones"] + +status_file_update_rate = 5 + +recording_term_size = (80, 24) + +max_connections = 100 + +# Script to initialize a user, e.g. make sure the paths +# and the rc file exist. This is not done by the server +# at the moment. +init_player_program = "./util/webtiles-init-player.sh" + +ssl_options = None # No SSL +#ssl_options = { +# "certfile": "./webserver/localhost.crt", +# "keyfile": "./webserver/localhost.key" +#} +ssl_address = "" +ssl_port = 8081 +# Or listen on multiple address/port pairs (overriding the above) with: +# ssl_bind_pairs = ( +# ("127.0.0.1", 8081), +# ("localhost", 8083), +# ) + +connection_timeout = 600 +max_idle_time = 5 * 60 * 60 + +use_gzip = True + +# Seconds until stale HTTP connections are closed +# This needs a patch currently not in mainline tornado. +http_connection_timeout = None + +# Set this to true if you are behind a reverse proxy +# Your proxy must set header X-Real-IP +# +# Enabling this option when webtiles is NOT protected behind a reverse proxy +# introduces a security risk. An attacker could inject a false address into the +# X-Real-IP header. Do not enable this option if the webtiles server is +# directly exposed to users. +http_xheaders = None + +kill_timeout = 10 # Seconds until crawl is killed after HUP is sent + +nick_regex = r"^[a-zA-Z0-9]{3,20}$" +max_passwd_length = 20 + +allow_password_reset = False # Set to true to allow users to request a password reset email. Some settings must be properly configured for this to work + +# Set to the primary URL where a player would reach the main lobby +# For example: "http://crawl.akrasiac.org/" +# This is required for for password reset, as it will be the base URL for +# recovery URLs. +lobby_url = None + +# Proper SMTP settings are required for password reset to function properly. +# if smtp_host is anything other than `localhost`, you may need to adjust the +# timeout settings (see server.py, calls to ioloop.set_blocking_log_threshold). +# TODO: set_blocking_log_threshold is deprecated in tornado 5+... +# Ideally, test out these settings carefully in a non-production setting +# before enabling this, as there's a bunch of ways for this to go wrong and you +# don't want to get your SMTP server blacklisted. +smtp_host = "localhost" +smtp_port = 25 +smtp_use_ssl = False +smtp_user = "" # set to None for no auth +smtp_password = "" +smtp_from_addr = "noreply@crawl.example.org" # The address from which automated + # emails will be sent + +# crypt() algorithm, e.g. "1" for MD5 or "6" for SHA-512; see crypt(3). If +# false, use traditional DES (but then only the first eight characters of the +# password are significant). If set to "broken", use traditional DES with +# the password itself as the salt; this is necessary for compatibility with +# dgamelaunch, but should be avoided if possible because it leaks the first +# two characters of the password's plaintext. +crypt_algorithm = "broken" + +# The length of the salt string to use. If crypt_algorithm is false, this +# setting is ignored and the salt is two characters. +crypt_salt_length = 16 + +login_token_lifetime = 7 # Days + +uid = None # If this is not None, the server will setuid to that (numeric) id +gid = None # after binding its sockets. + +umask = None # e.g. 0077 + +chroot = None + +pidfile = None +daemon = False # If true, the server will detach from the session after startup + +# Set to a URL with %s where lowercased player name should go in order to +# hyperlink WebTiles spectator names to their player pages. +# For example: "http://crawl.akrasiac.org/scoring/players/%s.html" +# Set to None to disable player page hyperlinks +player_url = None + +# Only for development: +# This is insecure; do not set development_mode = True in production! +development_mode = False + +# Disable caching of static files which are not part of game data. +no_cache = development_mode +# Automatically log in all users with the username given here. +autologin = None \ No newline at end of file diff --git a/webserver/games.d/base.yaml b/webserver/games.d/base.yaml new file mode 100644 index 0000000..fdc0a00 --- /dev/null +++ b/webserver/games.d/base.yaml @@ -0,0 +1,127 @@ +# Games are stored in order. The order of games you define in these files will +# be preserved, and the files will be loaded in sorted order. +# %n in paths and urls is replaced by the current username. +games: + # id must be unique for each game + - id: seeded-web-trunk + # The name for this game, displayed on the webtiles HTML interface + name: Custom seed + # Games are called with the following argv: + # [ + # $crawl_binary, + # *$pre_options, + # "-name", "%n", + # "-rc", "$rcfile_path/%n.rc", + # "-macro", "$macro_path/%n.macro", + # "-morgue", "$morgue_path," + # *$options, + # "-dir", "$dir_path" + # "-webtiles-socket", "$socket_path/%n:$timestamp.sock", + # "-await-connection" + # ] + # Relative to the server's CWD + crawl_binary: /app/bin/crawl + # rcfile_path, macro_path, morgue_path, and socket_path, and dir_path use + # server's CWD for relative paths, unless your crawl_binary command changes + # the CWD (like dgamelaunch-config's crawl-*-launcher.sh scripts do). + rcfile_path: /data/rcs/ + macro_path: /data/rcs/ + morgue_path: /data/rcs/%n + # Sockets are placed in this directory and used for communication between./webserver + # the webtiles server and DCSS executable. If you change the CWD with a + # crawl_binary script, this path will have to be absolute (so both the + # server and DCSS can find it). + socket_path: /data/rcs + # # The DCSS "dir" is where save games plus logfile/milestones files are + # # written. If you don't specify this, the game uses a compile-time default, + # # which depends on your OS and compilation settings. + # dir_path: . + # Directory where ttyrec files for active games are written to. + # Relative to the server's CWD. + inprogress_path: /data/rcs/running + # Directory where ttyrec files are stored for the individual user. + # Relative to the server's CWD. + ttyrec_path: /data/rcs/ttyrecs/%n + # Static content used by the game (eg spritesheets, game HTML). DCSS builds + # this as the web/ directory. + # Relative to the server's CWD. + client_path: /app/webserver/game_data/ + # # The working directory to run crawl from. You shouldn't really need to set + # # this, unless you are trying to run the circa 2016 fork DCSS Circus Animals + # # on a real webtiles server, and dont want save games littering your + # # webserver directory. + # # NOTE: if you set this, all settings which are documented as "relative to + # # the server's CWD" will become relative to the path here instead. This path + # # itself is relative to the server's CWD, though. + # cwd: . + # The URL shown to users when they create a character dump (`#`) ingame. You + # can use %n in it, like a directory. If you don't specify one, the user + # isn't told where their dump was written -- but it's still written. + morgue_url: None + # # The path to the game's "milestones" file. If you specify dir_path this can + # # be inferred automatically (since it's $dir_path/milestones), but otherwise + # # you should specify this explicitly. The milestones file is read to + # # populate the 'milestones' column in the HTML interface. If you don't + # # specify a milestones path, you'll just see nothing specified there. + # # (Note: DCSS writes both "milestones" and "milestones-seeded", we'll + # # automatically pick up both if you specify the former's path.) + # milestone_path: ./rcs/milestones + # # Array of extra options to add to the start of the DCSS command. Prefer + # # using 'options' unless the ordering is critical. + # pre_options: [] + # Array of extra options to add to the DCSS command. + options: + - -seed + # # Map of extra environment variables to set when executing the DCSS command. + # # All env vars from the webtiles server environment are automatically + # # inherited. + # env: + # LANG: en_US.UTF8 + # show_save_info: set to True if the binary supports save info json + # and you want it to be queried each time the player enters the lobby. + # (With a lot of binaries, it isn't necessarily recommended yet to blanket + # enable this, as it can slow down a player's lobby loading.) + show_save_info: True + # send_json_options is a legacy option. Always include this set to 'true'. + # (Or submit a PR to remove it.) + send_json_options: True + - id: tut-web-trunk + name: Tutorial + crawl_binary: /app/bin/crawl + rcfile_path: /data/rcs/ + macro_path: /data/rcs/ + morgue_path: /data/rcs/%n + inprogress_path: /data/rcs/running + ttyrec_path: /data/rcs/ttyrecs/%n + socket_path: /data/rcs + client_path: /app/webserver/game_data/ + # dir_path: . + # cwd: . + morgue_url: None + # milestone_path: ./rcs/milestones + show_save_info: True + send_json_options: True + options: + - -tutorial + # env: + # LANG: en_US.UTF8 + - id: sprint-web-trunk + name: Sprint + crawl_binary: /app/bin/crawl + rcfile_path: /data/rcs/ + macro_path: /data/rcs/ + morgue_path: /data/rcs/%n + inprogress_path: /data/rcs/running + ttyrec_path: /data/rcs/ttyrecs/%n + socket_path: /data/rcs + client_path: /app/webserver/game_data/ + # dir_path: . + # cwd: . + morgue_url: None + # milestone_path: ./rcs/milestones + show_save_info: True + send_json_options: True + options: + - -sprint + # env: + # LANG: en_US.UTF8 \ No newline at end of file