Files
dmtools/docker/setup.sh
T
FrankandClaude Sonnet 5 efba34a1e2 Deploy the puzzle relay alongside the container stack
Adds a puzzle-relay service (docker-compose.yml) running the same
image as php but executing app:puzzle-relay instead of php-fpm, kept
off the host network - only nginx can reach it. nginx proxies
wss://dmtools.fvandenberg.nl/puzzle-ws/ through to it
(docker/nginx/default.conf), with the public URL set via a new
.env.prod. setup.sh/restart.sh now rebuild, restart, and clean up the
new container alongside the existing ones.

Still needs "Websockets Support" enabled on the NPM proxy host for
this domain, or the upgrade headers never reach the container.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 13:36:38 +02:00

155 lines
5.5 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# Bootstraps the Dockerized dmtools stack (php, nginx, database) on a server.
# - Builds and starts the containers
# - Installs composer dependencies
# - Ensures APP_SECRET is set (generated into .env.local if empty)
# - Creates and migrates the database
# - Installs + compiles AssetMapper assets
# - Prints helpful info on success
#
# Usage:
# ./docker/setup.sh # full setup
# ./docker/setup.sh --no-build # skip image rebuild
# ./docker/setup.sh --recreate # force-recreate containers
# ./docker/setup.sh --down # stop and remove containers
#
# NGINX_PORT=8086 ./docker/setup.sh # if 8085 is already taken on this host
# (or set NGINX_PORT / DB_HOST_PORT once in .env.local and every run picks it up)
ROOT_DIR=$(cd "$(dirname "$0")"/.. && pwd)
PROJECT=dmtools
for var in NGINX_PORT DB_HOST_PORT; do
if [ -z "${!var:-}" ] && grep -q "^${var}=" "$ROOT_DIR/.env.local" 2>/dev/null; then
export "$var=$(grep "^${var}=" "$ROOT_DIR/.env.local" | tail -1 | cut -d= -f2-)"
fi
done
if docker compose version >/dev/null 2>&1; then
DOCKER_COMPOSE="docker compose"
elif command -v docker-compose >/dev/null 2>&1; then
DOCKER_COMPOSE="docker-compose"
else
echo "Error: Docker Compose not found." >&2
exit 1
fi
dc() { (cd "$ROOT_DIR" && $DOCKER_COMPOSE -p "$PROJECT" -f docker-compose.yml "$@"); }
REBUILD=1
RECREATE=0
DOWN_ONLY=0
for arg in "$@"; do
case "$arg" in
--no-build) REBUILD=0 ;;
--recreate) RECREATE=1 ;;
--down) DOWN_ONLY=1 ;;
*) echo "Unknown option: $arg" >&2; exit 1 ;;
esac
done
command -v docker >/dev/null 2>&1 || { echo "Error: docker is required." >&2; exit 1; }
if [ ! -f "$ROOT_DIR/.env.local" ]; then
echo "Error: .env.local is missing. Copy .env to .env.local and set real" >&2
echo " APP_SECRET, DB_PASSWORD and DB_ROOT_PASSWORD first." >&2
exit 1
fi
if [ "$DOWN_ONLY" -eq 1 ]; then
dc down
exit 0
fi
# docker-compose v1 can choke recreating a container in place on any config
# change; removing them first sidesteps that. Safe: state lives in named volumes.
dc rm -fs php puzzle-relay nginx database 2>/dev/null || true
BUILD_ARGS=()
[ "$REBUILD" -eq 1 ] && BUILD_ARGS+=("--build")
[ "$RECREATE" -eq 1 ] && BUILD_ARGS+=("--force-recreate")
dc up -d "${BUILD_ARGS[@]}"
# Setup one-offs run as root: the bind-mounted project dir is owned by the
# deploying user, not the image's www-data, so www-data can't write vendor/ etc.
pexec() { dc exec -T -u root php "$@"; }
printf "Waiting for database to be healthy..."
for i in {1..60}; do
id=$(dc ps -q database 2>/dev/null || true)
status=$([ -n "$id" ] && docker inspect -f '{{.State.Health.Status}}' "$id" 2>/dev/null || echo "")
if [ "$status" = "healthy" ]; then echo " OK"; break; fi
printf "."; sleep 2
[ "$i" -eq 60 ] && echo -e "\nWarning: database not healthy yet, continuing anyway."
done
pexec composer install --no-interaction
# Prod compiles config into a cached container under var/cache/prod/ (persistent
# php_var volume) and does NOT auto-detect config changes - clear it every run.
echo "Clearing and warming the cache..."
pexec php bin/console cache:clear --no-interaction
if grep -q '^APP_SECRET=$' "$ROOT_DIR/.env" 2>/dev/null \
&& ! grep -q '^APP_SECRET=' "$ROOT_DIR/.env.local" 2>/dev/null; then
echo "Generating APP_SECRET in .env.local..."
printf 'APP_SECRET=%s\n' "$(openssl rand -hex 16)" >> "$ROOT_DIR/.env.local"
dc up -d php puzzle-relay # pick up the new env value
fi
echo "Creating database if it doesn't exist..."
pexec php bin/console doctrine:database:create --if-not-exists
echo "Running migrations..."
pexec php bin/console doctrine:migrations:migrate -n --allow-no-migration
echo "Installing and compiling assets..."
pexec php bin/console importmap:install
pexec php bin/console asset-map:compile
# LAST: the root-run commands above leave new files under var/ root-owned;
# php-fpm runs as www-data and must be able to write there at runtime.
pexec chown -R www-data:www-data var
# php.ini sets opcache.validate_timestamps=0, so php-fpm keeps serving the
# old compiled code/templates until it restarts. Bounce it so a --no-build
# run (git pull + this script) actually picks up the new cache. puzzle-relay
# is a single long-running process, not php-fpm workers, but it's exactly as
# stale otherwise: it keeps running whatever code was loaded when it started.
dc restart php puzzle-relay
# Make sure Nginx Proxy Manager can reach this stack's nginx by name.
# Harmless (and a no-op) if already connected; NPM keeps it across restarts.
NPM_CONTAINER="${NPM_CONTAINER:-nginx_app_1}"
if docker inspect "$NPM_CONTAINER" >/dev/null 2>&1; then
docker network connect "${PROJECT}_default" "$NPM_CONTAINER" 2>/dev/null \
&& echo "Connected $NPM_CONTAINER to ${PROJECT}_default." \
|| true
fi
APP_URL="http://localhost:${NGINX_PORT:-8085}"
cat <<EOT
Setup complete!
Open the app: $APP_URL (real access is via https://dmtools.fvandenberg.nl through NPM)
NPM proxy host: scheme http, forward "dmtools-nginx" port 80.
If NPM ($NPM_CONTAINER) can't reach it, run:
docker network connect ${PROJECT}_default $NPM_CONTAINER
Create the first user:
$DOCKER_COMPOSE -p $PROJECT exec php php bin/console app:user:create you@example.com --admin
Common commands (from the project root):
$DOCKER_COMPOSE -p $PROJECT logs -f nginx
$DOCKER_COMPOSE -p $PROJECT logs -f php
$DOCKER_COMPOSE -p $PROJECT exec php bash
$DOCKER_COMPOSE -p $PROJECT down
Re-run this script any time. Use --no-build to skip rebuilding images.
EOT