NPM (nginx_app_1) is connected to each stack's own default network by hand, not to a shared docker_default. Drop the unused external `proxy` network from the compose file; nginx just lives on dmtools_default. setup.sh now connects NPM to it (no-op if already connected) and prints the proxy-host settings. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
148 lines
5.0 KiB
Bash
Executable File
148 lines
5.0 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 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 # 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
|
|
|
|
# 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
|