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>
155 lines
5.5 KiB
Bash
Executable File
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
|