v0.12.2 drop version from api path, deploy script update

This commit is contained in:
lixen committed 2026-09-27 02:30:00 -04:00
1 parent 1e7d759a19
commit 52ac0fe31a
24 files changed
+658 -650

No files matched your search

+46 -13
View File
@@ -1,19 +1,52 @@
# Host nginx: proxy /chess/ to the chess API in the jail. Include inside the
# existing TLS server block, next to the security headers. Replace
# JAIL_IP:API_PORT with the values given to setup-jail.sh.
# Host nginx for chess: browsers (web and WASM clients) call
# <origin>/chess/api/... and <origin>/chess/health, which map to the server's
# /api/... and /health. Replace <jail-address> and <port> (chessd_port,
# default 8080).
#
# chess-server trusts X-Real-IP only from the address in -trusted-proxies
# (TRUSTED_PROXIES in setup-jail.sh): the address this nginx uses to reach
# the jail. Do not forward X-Forwarded-For for rate limiting; its first entry
# is supplied by the client.
location /chess/ {
proxy_pass http://JAIL_IP:API_PORT/;
# chess-server keys rate limits on X-Real-IP, trusted only from the addresses
# in chessd_trusted_proxies: the address this nginx uses to reach the jail.
# $remote_addr must be the real client: with PROXY protocol in front of nginx,
# set real_ip_header proxy_protocol and set_real_ip_from for that hop.
# --- http {} context ---
upstream chess_api {
server <jail-address>:<port>;
keepalive 16;
}
# --- TLS server {} context ---
location /chess/api/ {
proxy_pass http://chess_api/api/;
proxy_http_version 1.1;
# Empty Connection header lets nginx reuse upstream keepalive connections.
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
# Long-polling holds a request up to 30 seconds.
proxy_buffering off;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
# API payloads are small JSON documents.
client_max_body_size 16k;
}
location = /chess/health {
proxy_pass http://chess_api/health;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# Long-polling waits up to 30 seconds for a game update.
proxy_read_timeout 45s;
# Bound request bodies; API payloads are small JSON documents.
client_max_body_size 16k;
proxy_read_timeout 10s;
}
# Everything else under /chess is not served by the API.
location = /chess {
return 404;
}
location /chess/ {
return 404;
}
-36
View File
@@ -1,36 +0,0 @@
#!/bin/sh
#
# PROVIDE: chess_server
# REQUIRE: LOGIN postgresql
# KEYWORD: shutdown
#
# rc.conf variables (setup-jail.sh sets them with sysrc):
# chess_server_enable YES to start at boot
# chess_server_flags chess-server arguments (-dsn, -jwt-secret-file, ...)
# chess_server_env environment, including PATH=... for stockfish
# chess_server_account unprivileged account (default: chess)
# chess_server_binary default: /usr/local/chess/chess-server
# chess_server_logfile default: /var/log/chess/chess-server.log
#
# daemon(8) starts as root, writes the supervisor pidfile, drops to the
# account, restarts the server if it exits (-r, 5 s delay), and reopens the
# log file on SIGHUP (-H) for newsyslog. Do not set chess_server_user: rc.subr
# would switch users before daemon runs and daemon could not drop privileges.
. /etc/rc.subr
name=chess_server
rcvar=chess_server_enable
load_rc_config $name
: ${chess_server_enable:="NO"}
: ${chess_server_account:="chess"}
: ${chess_server_binary:="/usr/local/chess/chess-server"}
: ${chess_server_logfile:="/var/log/chess/chess-server.log"}
pidfile="/var/run/${name}.pid"
procname="daemon"
command="/usr/sbin/daemon"
command_args="-f -r -R 5 -H -P ${pidfile} -o ${chess_server_logfile} -u ${chess_server_account} ${chess_server_binary} ${chess_server_flags}"
run_rc_command "$1"
+128
View File
@@ -0,0 +1,128 @@
#!/bin/sh
#
# PROVIDE: chessd
# REQUIRE: DAEMON NETWORKING postgresql
# KEYWORD: shutdown
#
# chessd_enable: Set to YES to enable chessd on startup.
# chessd_user: User to run as. Default: chess
# chessd_group: Group to run as. Default: chess
# chessd_home: Account home. Default: /home/chess
# chessd_bin: Server binary. Default: ${chessd_home}/bin/chess-server
# chessd_logs: Log file path. Default: /var/log/chessd.log
# chessd_host: Listen address. Default: 0.0.0.0
# chessd_port: Listen port. Default: 8080
# chessd_dsn: PostgreSQL connection. Default: postgres:///chess?host=/tmp
# (database chess over the Unix socket, as the OS user)
# chessd_jwt_key: JWT signing key, created on first start (mode 0600).
# Default: ${chessd_home}/jwt.key
# chessd_trusted_proxies: Reverse-proxy address(es) whose X-Real-IP header is
# trusted as the client address. Default: none
# chessd_flags: Additional flags passed to the binary. Default: ""
#
# Add the following to /etc/rc.conf to enable:
# chessd_enable="YES"
# chessd_trusted_proxies="<address nginx connects from>"
. /etc/rc.subr
name="chessd"
rcvar="chessd_enable"
desc="Chess Server Daemon"
load_rc_config "$name"
: ${chessd_enable:="NO"}
: ${chessd_user:="chess"}
: ${chessd_group:="chess"}
: ${chessd_home:="/home/chess"}
: ${chessd_bin:="${chessd_home}/bin/chess-server"}
: ${chessd_logs:="/var/log/chessd.log"}
: ${chessd_host:="0.0.0.0"}
: ${chessd_port:="8080"}
: ${chessd_dsn:="postgres:///chess?host=/tmp"}
: ${chessd_jwt_key:="${chessd_home}/jwt.key"}
: ${chessd_trusted_proxies:=""}
: ${chessd_flags:=""}
command="${chessd_bin}"
pidfile="/var/run/${name}.pid"
start_precmd="${name}_prestart"
start_cmd="${name}_start"
stop_cmd="${name}_stop"
status_cmd="${name}_status"
chessd_prestart()
{
if [ ! -x "${command}" ]; then
err 1 "${command} is missing or not executable"
fi
touch "${chessd_logs}"
chown "${chessd_user}:${chessd_group}" "${chessd_logs}"
chmod 640 "${chessd_logs}"
# A stable signing key keeps sessions valid across restarts. The server
# refuses a key readable by group or others.
if [ ! -f "${chessd_jwt_key}" ]; then
(umask 077 && /usr/bin/openssl rand -base64 48 > "${chessd_jwt_key}") ||
err 1 "cannot create ${chessd_jwt_key}"
chown "${chessd_user}:${chessd_group}" "${chessd_jwt_key}"
echo "Created JWT signing key ${chessd_jwt_key}."
fi
}
chessd_start()
{
if chessd_status >/dev/null 2>&1; then
echo "${desc} is already running as PID $(cat ${pidfile})."
return 0
fi
set -- -api-host "${chessd_host}" -api-port "${chessd_port}" \
-dsn "${chessd_dsn}" -jwt-secret-file "${chessd_jwt_key}"
if [ -n "${chessd_trusted_proxies}" ]; then
set -- "$@" -trusted-proxies "${chessd_trusted_proxies}"
fi
echo "Starting ${desc}."
# -r restarts the server 5 seconds after an unexpected exit (for example
# when PostgreSQL is not yet accepting connections); -H reopens the log
# file on SIGHUP so newsyslog can rotate it.
/usr/sbin/daemon \
-P "${pidfile}" \
-r -R 5 \
-H \
-u "${chessd_user}" \
-o "${chessd_logs}" \
-f \
/usr/bin/env \
HOME="${chessd_home}" \
PATH="${chessd_home}/bin:/usr/local/bin:/usr/bin:/bin" \
"${command}" "$@" ${chessd_flags}
}
chessd_stop()
{
if [ -f "${pidfile}" ]; then
echo "Stopping ${desc}."
kill -TERM "$(cat ${pidfile})" 2>/dev/null
wait_for_pids "$(cat ${pidfile})"
rm -f "${pidfile}"
else
echo "${desc} is not running (no pidfile)."
fi
}
chessd_status()
{
if [ -f "${pidfile}" ] && kill -0 "$(cat ${pidfile})" 2>/dev/null; then
echo "${desc} is running as PID $(cat ${pidfile})."
else
echo "${desc} is not running."
return 1
fi
}
run_rc_command "$1"
+187 -183
View File
@@ -1,42 +1,41 @@
#!/bin/sh
# Install or update chess-server with PostgreSQL 18 inside a FreeBSD jail.
# Run as root inside the jail, from a copy of this repository:
# Install or upgrade chess-server (rc.d service `chessd`) with PostgreSQL
# inside a FreeBSD jail. Run as root inside the jail from a copy of this
# repository:
#
# CHESS_BINARY=/tmp/chess-server-freebsd-amd64 \
# API_HOST=10.17.89.10 TRUSTED_PROXIES=10.17.89.1 \
# sh deploy/freebsd/setup-jail.sh
# CHESS_BINARY=/tmp/chess-server TRUSTED_PROXIES=<nginx-address> \
# sh deploy/freebsd/setup-jail.sh
#
# Before the first run:
# 1. On the host: sh deploy/freebsd/host.sh <jail> (sysvshm=new, restart)
# 2. On a build machine: make server-freebsd, then copy
# bin/chess-server-freebsd-amd64 into the jail
# 3. For an existing SQLite deployment: note the database path; the old
# service is stopped before the import
# Prerequisites:
# - postgresql18-server, postgresql18-client, and stockfish packages
# - the jail has its own System V shared memory (deploy/freebsd/host.sh)
# - the release binary, built with `make server-freebsd` (or `make server`
# on FreeBSD)
#
# Required:
# CHESS_BINARY new chess-server binary (FreeBSD/amd64, static)
# API_HOST address chess-server listens on (the jail IP)
# Inputs (environment):
# CHESS_BINARY required: chess-server binary to install
# TRUSTED_PROXIES address(es) the host nginx connects from, stored as
# chessd_trusted_proxies in rc.conf. Without it, every
# proxied client shares one rate-limit bucket.
# SPLIT_PRIVILEGES yes|no (default no); see deploy/postgresql/setup.sql
# ENABLE_LOG_ROTATION yes|no (default no): newsyslog entry for the log
# ENABLE_BACKUP yes|no (default no): nightly pg_dump via cron
#
# Optional:
# API_PORT default 8080
# TRUSTED_PROXIES host nginx address(es) as seen from the jail; without it
# all proxied clients share one rate-limit bucket
# CHESS_HOME default: the chess account's home, else /usr/local/chess
# LOG_DIR default /var/log/chess
# SQLITE_DB v0.11 SQLite database to import once into an empty schema
# OLD_SERVICE rc.d name of the previous chess service to stop and
# disable (for example chess); skipped when unset
# SPLIT_PRIVILEGES yes: runtime role gets DML only, schema owned by
# chess_owner (default no; see deploy/postgresql/setup.sql)
# EXTRA_FLAGS additional chess-server flags (e.g. "-max-users 500")
# PGDATA default /var/db/postgres/data18
# The service layout comes from rc.conf (chessd_*) with the defaults of
# deploy/freebsd/rc.d/chessd: account chess, home /home/chess, binary
# ~/bin/chess-server, log /var/log/chessd.log, 0.0.0.0:8080.
#
# The script is idempotent: rerun it to upgrade the binary, change flags, or
# repair configuration. It never drops data. Steps:
# packages -> PostgreSQL init and hardening -> role/database/schema ->
# chess account, binary, JWT key, log dir -> stop old service -> schema
# migration -> optional SQLite import -> rc.d/rc.conf -> log rotation ->
# nightly backup -> start and health check
# Steps (idempotent; rerun to upgrade):
# 1. PostgreSQL: initdb only if no cluster exists; pg_hba.conf allows only
# postgres and chess over the Unix socket (peer); TCP listener disabled
# 2. role, database, and schema from deploy/postgresql/setup.sql (first run)
# 3. stop chessd; install the binary (previous kept as .prev) and the rc.d
# script (previous copy saved under /var/backups)
# 4. JWT key; schema migration; rc.conf settings
# 5. optional log rotation and backups
# 6. start chessd and check /health
# Files of a previous SQLite release are left untouched; remove them once the
# new release is verified.
set -eu
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
umask 022
@@ -44,73 +43,96 @@ umask 022
here=$(cd "$(dirname "$0")" && pwd)
repo=$(cd "$here/../.." && pwd)
: "${CHESS_BINARY:?set CHESS_BINARY to the new chess-server binary}"
: "${API_HOST:?set API_HOST to the address chess-server listens on}"
API_PORT=${API_PORT:-8080}
: "${CHESS_BINARY:?set CHESS_BINARY to the chess-server binary to install}"
TRUSTED_PROXIES=${TRUSTED_PROXIES:-}
LOG_DIR=${LOG_DIR:-/var/log/chess}
SQLITE_DB=${SQLITE_DB:-}
OLD_SERVICE=${OLD_SERVICE:-}
SPLIT_PRIVILEGES=${SPLIT_PRIVILEGES:-no}
EXTRA_FLAGS=${EXTRA_FLAGS:-}
PGDATA=${PGDATA:-/var/db/postgres/data18}
# Install locations; overridable only for testing the script.
ENABLE_LOG_ROTATION=${ENABLE_LOG_ROTATION:-no}
ENABLE_BACKUP=${ENABLE_BACKUP:-no}
# System locations; overridable only to test the script.
RC_DIR=${RC_DIR:-/usr/local/etc/rc.d}
RC_BACKUP_DIR=${RC_BACKUP_DIR:-/var/backups}
NEWSYSLOG_DIR=${NEWSYSLOG_DIR:-/usr/local/etc/newsyslog.conf.d}
CRON_DIR=${CRON_DIR:-/usr/local/etc/cron.d}
SBIN_DIR=${SBIN_DIR:-/usr/local/sbin}
BACKUP_DIR=${BACKUP_DIR:-/var/db/postgres/backups}
# The database role is named after the OS account: peer authentication maps
# one to the other, so no password exists anywhere.
account=chess
dsn='postgres:///chess?host=/tmp'
log() { printf '==> %s\n' "$*"; }
die() { printf 'setup-jail: %s\n' "$*" >&2; exit 1; }
# Run SQL from stdin as the postgres superuser against database $1.
# SQL from stdin, run as the postgres superuser against database $1.
pg_sql() {
su -m postgres -c "psql -X -A -t -q -v ON_ERROR_STOP=1 -d $1"
}
# --- preflight ---------------------------------------------------------------
# rc.conf value of $1, or the default $2.
rc_value() {
_value=$(sysrc -n "$1" 2>/dev/null) || _value=""
if [ -n "$_value" ]; then echo "$_value"; else echo "$2"; fi
}
yes_no() {
case $2 in yes | no) ;; *) die "$1 must be yes or no" ;; esac
}
# --- preflight -----------------------------------------------------------------
[ "$(id -u)" -eq 0 ] || die "run as root"
[ "$(uname -s)" = FreeBSD ] || die "this script targets FreeBSD"
[ -f "$CHESS_BINARY" ] || die "CHESS_BINARY not found: $CHESS_BINARY"
for f in "$repo/deploy/postgresql/setup.sql" "$repo/deploy/postgresql/migrate-sqlite.sh" \
"$here/rc.d/chess_server" "$here/chess-backup.sh"; do
for f in "$repo/deploy/postgresql/setup.sql" "$here/rc.d/chessd" "$here/chess-backup.sh"; do
[ -f "$f" ] || die "missing $f; run from a complete repository copy"
done
case $SPLIT_PRIVILEGES in yes | no) ;; *) die "SPLIT_PRIVILEGES must be yes or no" ;; esac
if [ -n "$SQLITE_DB" ] && [ ! -r "$SQLITE_DB" ]; then
die "SQLITE_DB not readable: $SQLITE_DB"
yes_no SPLIT_PRIVILEGES "$SPLIT_PRIVILEGES"
yes_no ENABLE_LOG_ROTATION "$ENABLE_LOG_ROTATION"
yes_no ENABLE_BACKUP "$ENABLE_BACKUP"
command -v postgres >/dev/null 2>&1 || die "PostgreSQL is not installed (pkg install postgresql18-server postgresql18-client)"
pg_major=$(postgres --version | sed -n 's/^postgres (PostgreSQL) \([0-9][0-9]*\).*/\1/p')
[ -n "$pg_major" ] && [ "$pg_major" -ge 17 ] || die "PostgreSQL 17 or newer is required (found: $(postgres --version))"
command -v stockfish >/dev/null 2>&1 || log "WARNING: stockfish not found in PATH; computer moves will fail"
user=$(rc_value chessd_user chess)
group=$(rc_value chessd_group chess)
[ "$user" = chess ] || die "chessd_user is $user; peer authentication requires the OS account and database role to both be chess"
if ! id "$user" >/dev/null 2>&1; then
log "creating account $user (home /home/$user, no shell, no password)"
pw useradd -n "$user" -c "Chess server" -d "/home/$user" -m -s /usr/sbin/nologin -w no
fi
[ -n "$TRUSTED_PROXIES" ] ||
log "WARNING: TRUSTED_PROXIES unset; behind nginx every client shares one rate limit"
# chessd_home: rc.conf, else the account's home. rc.d/chessd defaults to
# /home/chess, so any other home is recorded in rc.conf.
account_home=$(pw usershow "$user" | cut -d: -f9)
home=$(rc_value chessd_home "$account_home")
if [ "$home" != /home/chess ] && [ -z "$(rc_value chessd_home "")" ]; then
sysrc -q chessd_home="$home" >/dev/null
fi
bin=$(rc_value chessd_bin "$home/bin/chess-server")
host=$(rc_value chessd_host 0.0.0.0)
port=$(rc_value chessd_port 8080)
dsn=$(rc_value chessd_dsn 'postgres:///chess?host=/tmp')
key=$(rc_value chessd_jwt_key "$home/jwt.key")
# --- packages ----------------------------------------------------------------
packages="postgresql18-server postgresql18-client stockfish"
[ -n "$SQLITE_DB" ] && packages="$packages sqlite3"
log "installing packages: $packages"
# shellcheck disable=SC2086 # word splitting intended
pkg install -y $packages
case $(rc_value chessd_flags "") in
*-storage-path*) die "rc.conf chessd_flags contains -storage-path, which no longer exists; remove it (sysrc -x chessd_flags) and rerun" ;;
esac
# --- PostgreSQL ----------------------------------------------------------------
# --- PostgreSQL -------------------------------------------------------------------
pgdata=$(rc_value postgresql_data "/var/db/postgres/data$pg_major")
if [ ! -f "$pgdata/PG_VERSION" ]; then
log "initializing PostgreSQL cluster in $pgdata"
sysrc -q postgresql_enable=YES >/dev/null
sysrc -q postgresql_initdb_flags="--encoding=UTF8 --locale-provider=builtin --builtin-locale=C.UTF-8 --auth-local=peer --auth-host=reject" >/dev/null
service postgresql initdb ||
die "initdb failed; a shared memory error means the jail needs sysvshm=new (deploy/freebsd/host.sh)"
fi
sysrc -q postgresql_enable=YES >/dev/null
sysrc -q postgresql_data="$PGDATA" >/dev/null
sysrc -q postgresql_initdb_flags="--encoding=UTF8 --locale-provider=builtin --builtin-locale=C.UTF-8 --auth-local=peer --auth-host=reject" >/dev/null
if [ ! -f "$PGDATA/PG_VERSION" ]; then
log "initializing PostgreSQL cluster in $PGDATA"
if ! service postgresql initdb; then
die "initdb failed. If it reports a shared memory error, run deploy/freebsd/host.sh <jail> on the host (sysvshm=new) and retry"
fi
if ! service postgresql status >/dev/null 2>&1; then
log "starting PostgreSQL"
service postgresql start
fi
# Only the postgres superuser and the chess role, each over the Unix socket
# with peer authentication. No host lines: TCP is disabled below.
hba="$PGDATA/pg_hba.conf"
# with peer authentication. A cluster initialized with FreeBSD's default flags
# trusts every local connection; this replaces that.
hba="$pgdata/pg_hba.conf"
if ! grep -q '^# Managed by chess setup-jail.sh' "$hba" 2>/dev/null; then
[ -f "$hba.orig" ] || cp -p "$hba" "$hba.orig"
log "writing $hba (original kept as pg_hba.conf.orig)"
@@ -122,163 +144,145 @@ local chess chess peer
HBA
chown postgres:postgres "$hba"
chmod 600 "$hba"
service postgresql reload
fi
log "starting PostgreSQL"
service postgresql status >/dev/null 2>&1 || service postgresql start
# ALTER SYSTEM writes postgresql.auto.conf, leaving postgresql.conf untouched.
pg_sql postgres <<'SQL'
# Unix socket only. ALTER SYSTEM writes postgresql.auto.conf and leaves
# postgresql.conf untouched; the setting needs a restart.
if [ -n "$(echo 'SHOW listen_addresses' | pg_sql postgres)" ]; then
log "disabling the PostgreSQL TCP listener"
pg_sql postgres <<'SQL'
ALTER SYSTEM SET listen_addresses = '';
ALTER SYSTEM SET unix_socket_directories = '/tmp';
ALTER SYSTEM SET password_encryption = 'scram-sha-256';
SQL
# listen_addresses and unix_socket_directories apply only after a restart.
service postgresql restart
service postgresql restart
if [ -n "$(echo 'SHOW listen_addresses' | pg_sql postgres)" ]; then
log "WARNING: listen_addresses is still set, from a source that overrides postgresql.auto.conf (postgresql_flags?); PostgreSQL still listens on TCP"
fi
fi
if [ -z "$(echo "SELECT 1 FROM pg_roles WHERE rolname = 'chess'" | pg_sql postgres)" ]; then
have_role=$(echo "SELECT 1 FROM pg_roles WHERE rolname = 'chess'" | pg_sql postgres)
have_db=$(echo "SELECT 1 FROM pg_database WHERE datname = 'chess'" | pg_sql postgres)
case "$have_role$have_db" in
11) log "role and database chess exist; provisioning unchanged" ;;
"")
log "provisioning role, database, and schema (split privileges: $SPLIT_PRIVILEGES)"
split=false
[ "$SPLIT_PRIVILEGES" = yes ] && split=true
su -m postgres -c "psql -X -q -d postgres -v split=$split -f -" <"$repo/deploy/postgresql/setup.sql"
else
log "role chess exists; leaving database provisioning unchanged"
;;
*) die "only one of role chess and database chess exists; inspect with: su -m postgres -c psql" ;;
esac
# --- chessd --------------------------------------------------------------------
if service chessd onestatus >/dev/null 2>&1; then
log "stopping chessd"
service chessd onestop
fi
# --- chess account and files ---------------------------------------------------
if ! id "$account" >/dev/null 2>&1; then
CHESS_HOME=${CHESS_HOME:-/usr/local/chess}
log "creating account $account (home $CHESS_HOME, no shell, no password)"
pw useradd -n "$account" -c "Chess server" -d "$CHESS_HOME" -s /usr/sbin/nologin -w no
else
CHESS_HOME=${CHESS_HOME:-$(pw usershow "$account" | cut -d: -f9)}
shell=$(pw usershow "$account" | cut -d: -f10)
case $shell in
*/nologin | */false) ;;
*) log "WARNING: $account has login shell $shell; consider: pw usermod $account -s /usr/sbin/nologin" ;;
esac
bindir=$(dirname "$bin")
[ -d "$bindir" ] || install -d -o root -g wheel -m 0755 "$bindir"
if [ -f "$bin" ] && ! cmp -s "$CHESS_BINARY" "$bin"; then
log "keeping the previous binary as $bin.prev"
cp -p "$bin" "$bin.prev"
fi
[ -d "$CHESS_HOME" ] || install -d -o root -g "$account" -m 0750 "$CHESS_HOME"
install -d -o "$account" -g "$account" -m 0750 "$LOG_DIR"
# Root-owned: the service account cannot replace its own executable file.
install -o root -g wheel -m 0555 "$CHESS_BINARY" "$bin"
binary="$CHESS_HOME/chess-server"
if [ -f "$binary" ] && ! cmp -s "$CHESS_BINARY" "$binary"; then
log "keeping the previous binary as $binary.prev"
cp -p "$binary" "$binary.prev"
[ -d "$RC_DIR" ] || install -d "$RC_DIR"
if [ -f "$RC_DIR/chessd" ] && ! cmp -s "$here/rc.d/chessd" "$RC_DIR/chessd"; then
# Saved outside rc.d: rc(8) would run a second copy found there.
[ -d "$RC_BACKUP_DIR" ] || install -d -m 0750 "$RC_BACKUP_DIR"
saved="$RC_BACKUP_DIR/chessd.rc.$(date -u +%Y%m%dT%H%M%SZ)"
cp -p "$RC_DIR/chessd" "$saved"
log "previous rc.d script saved as $saved"
fi
# Root-owned: the service account cannot replace its own executable.
install -o root -g wheel -m 0555 "$CHESS_BINARY" "$binary"
install -o root -g wheel -m 0555 "$here/rc.d/chessd" "$RC_DIR/chessd"
key="$CHESS_HOME/jwt.key"
if [ ! -f "$key" ]; then
log "generating JWT signing key $key"
(umask 077 && openssl rand -base64 48 >"$key")
fi
chown "$account:$account" "$key"
chown "$user:$group" "$key"
chmod 600 "$key"
# --- stop the previous service ------------------------------------------------
if [ -n "$OLD_SERVICE" ] && [ "$OLD_SERVICE" != chess_server ]; then
log "stopping and disabling previous service $OLD_SERVICE"
service "$OLD_SERVICE" onestop || true
sysrc -q "${OLD_SERVICE}_enable=NO" >/dev/null || true
fi
service chess_server onestatus >/dev/null 2>&1 && service chess_server onestop
# --- schema ------------------------------------------------------------------
log "creating or migrating the chess schema"
if [ "$SPLIT_PRIVILEGES" = yes ]; then
# The postgres account migrates as chess_owner. It may not be able to
# traverse the chess home, so it runs a temporary copy of the binary.
# The postgres account migrates as chess_owner. It may be unable to reach
# the chess home, so it runs a temporary copy of the binary.
migrator=$(mktemp /tmp/chess-migrate.XXXXXX)
install -o root -g wheel -m 0555 "$CHESS_BINARY" "$migrator"
su -m postgres -c "$migrator db init -dsn \"dbname=chess user=postgres options='-c role=chess_owner -c search_path=chess'\"" ||
{ rm -f "$migrator"; die "schema migration failed"; }
rm -f "$migrator"
else
su -m "$account" -c "$binary db init -dsn '$dsn'"
su -m "$user" -c "$bin db init -dsn '$dsn'"
fi
# --- optional SQLite import ----------------------------------------------------
if [ -n "$SQLITE_DB" ]; then
if [ -n "$(echo 'SELECT 1 FROM chess.users UNION ALL SELECT 1 FROM chess.games LIMIT 1' | pg_sql chess)" ]; then
log "chess schema already has data; skipping SQLite import"
else
log "importing $SQLITE_DB"
work=$(mktemp -d /tmp/chess-import.XXXXXX)
# Copy WAL companions too: after an unclean stop they hold recent writes.
for suffix in "" -wal -shm; do
if [ -f "$SQLITE_DB$suffix" ]; then
cp "$SQLITE_DB$suffix" "$work/legacy.db$suffix"
fi
done
chown -R postgres "$work"
su -m postgres -c "cd '$work' && CHESS_SCHEMA=chess sh -s -- '$work/legacy.db' dbname=chess" \
<"$repo/deploy/postgresql/migrate-sqlite.sh"
rm -rf "$work"
sysrc -q chessd_enable=YES >/dev/null
if [ -n "$TRUSTED_PROXIES" ]; then
sysrc -q chessd_trusted_proxies="$TRUSTED_PROXIES" >/dev/null
fi
[ -n "$(rc_value chessd_trusted_proxies "")" ] ||
log "WARNING: chessd_trusted_proxies is unset; behind nginx every client shares one rate limit"
# Settings of the SQLite-era rc.d script that nothing reads any more.
for stale in chessd_storage_path chessd_dir; do
if sysrc -n "$stale" >/dev/null 2>&1; then
sysrc -q -x "$stale" >/dev/null
log "removed unused rc.conf setting $stale"
fi
done
# --- optional maintenance -------------------------------------------------------------
logs=$(rc_value chessd_logs /var/log/chessd.log)
if [ "$ENABLE_LOG_ROTATION" = yes ]; then
# Daily or at 1 MB, keep 7; SIGHUP makes daemon(8) (-H) reopen the log.
[ -d "$NEWSYSLOG_DIR" ] || install -d "$NEWSYSLOG_DIR"
printf '%s\n' \
"# Managed by chess setup-jail.sh" \
"$logs $user:$group 640 7 1000 @T00 JC /var/run/chessd.pid 1" \
>"$NEWSYSLOG_DIR/chessd.conf"
log "log rotation: $NEWSYSLOG_DIR/chessd.conf"
fi
if [ "$ENABLE_BACKUP" = yes ]; then
# Nightly pg_dump at 03:30 as postgres, keeping 14 days.
[ -d "$SBIN_DIR" ] || install -d "$SBIN_DIR"
install -o root -g wheel -m 0555 "$here/chess-backup.sh" "$SBIN_DIR/chess-backup"
[ -d "$BACKUP_DIR" ] || install -d -o postgres -g postgres -m 0700 "$BACKUP_DIR"
[ -d "$CRON_DIR" ] || install -d "$CRON_DIR"
printf '%s\n' \
"# Managed by chess setup-jail.sh" \
"SHELL=/bin/sh" \
"PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin" \
"30 3 * * * postgres CHESS_BACKUP_DIR=$BACKUP_DIR $SBIN_DIR/chess-backup" \
>"$CRON_DIR/chess-backup"
log "backups: $CRON_DIR/chess-backup -> $BACKUP_DIR"
fi
# --- service configuration -------------------------------------------------------
if [ -f "$RC_DIR/chess_server" ] && ! cmp -s "$here/rc.d/chess_server" "$RC_DIR/chess_server"; then
cp -p "$RC_DIR/chess_server" "$RC_DIR/chess_server.orig"
fi
install -d "$RC_DIR"
install -o root -g wheel -m 0555 "$here/rc.d/chess_server" "$RC_DIR/chess_server"
flags="-api-host $API_HOST -api-port $API_PORT -dsn $dsn -jwt-secret-file $key -log-level info"
[ -n "$TRUSTED_PROXIES" ] && flags="$flags -trusted-proxies $TRUSTED_PROXIES"
[ -n "$EXTRA_FLAGS" ] && flags="$flags $EXTRA_FLAGS"
sysrc -q chess_server_enable=YES >/dev/null
sysrc -q chess_server_account="$account" >/dev/null
sysrc -q chess_server_binary="$binary" >/dev/null
sysrc -q chess_server_logfile="$LOG_DIR/chess-server.log" >/dev/null
sysrc -q chess_server_env="PATH=/usr/local/bin:/usr/bin:/bin" >/dev/null
sysrc -q chess_server_flags="$flags" >/dev/null
# Rotate daily or at 1 MB, keep a week; SIGHUP makes daemon(8) reopen the log.
install -d "$NEWSYSLOG_DIR"
printf '%s\n' \
"# Managed by chess setup-jail.sh" \
"$LOG_DIR/chess-server.log $account:$account 640 7 1000 @T00 JC /var/run/chess_server.pid 1" \
>"$NEWSYSLOG_DIR/chess_server.conf"
# Nightly pg_dump at 03:30, as postgres, keeping 14 days.
install -d "$SBIN_DIR"
install -o root -g wheel -m 0555 "$here/chess-backup.sh" "$SBIN_DIR/chess-backup"
install -d -o postgres -g postgres -m 0700 "$BACKUP_DIR"
install -d "$CRON_DIR"
printf '%s\n' \
"# Managed by chess setup-jail.sh" \
"SHELL=/bin/sh" \
"PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin" \
"30 3 * * * postgres CHESS_BACKUP_DIR=$BACKUP_DIR $SBIN_DIR/chess-backup" \
>"$CRON_DIR/chess-backup"
# --- start and verify -----------------------------------------------------------
log "starting chess_server"
service chess_server start
# --- start and verify -----------------------------------------------------------------
log "starting chessd"
service chessd start
health_host=$host
[ "$health_host" = 0.0.0.0 ] && health_host=127.0.0.1
url="http://$health_host:$port/health"
health=""
for _ in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do
if health=$(fetch -q -T 2 -o - "http://$API_HOST:$API_PORT/health" 2>/dev/null); then
if health=$(fetch -q -T 2 -o - "$url" 2>/dev/null); then
break
fi
sleep 2
done
case $health in
*'"storage":"ok"'*) log "healthy: $health" ;;
*) die "chess-server did not report healthy storage (got: ${health:-no response}); see $LOG_DIR/chess-server.log" ;;
*) die "no healthy response from $url (got: ${health:-nothing}); see $logs, and check that the jail firewall allows loopback" ;;
esac
cat <<EOF
chess-server is running at http://$API_HOST:$API_PORT with PostgreSQL storage.
Remaining host step: add the location block from deploy/freebsd/nginx-chess.conf
(proxy_pass http://$API_HOST:$API_PORT/) to the host nginx, then
nginx -t && service nginx reload
chessd is running with PostgreSQL storage ($url).
Administration inside the jail:
su -m $account -c '$binary db user list -dsn "$dsn"'
service chess_server status|restart
tail -f $LOG_DIR/chess-server.log
su -m $user -c '$bin db user list -dsn "$dsn"'
service chessd status|restart
tail -f $logs
EOF