feat: add self-bootstrapping dev server script (bin/dev.sh)
Tests / test (pull_request) Successful in 50s
Push Develop / docker (push) Successful in 8m39s
Sync GitHub / sync (push) Successful in 7s
Tests / test (push) Successful in 57s

Manages a local PHP dev server for end-to-end development and testing.
Binds to 0.0.0.0:8773, accessible via Caddy at
https://preauth.lyra-dev.devgnome.com.

Features:
- Self-bootstrapping: installs PHP 8.4 + extensions (including APCu,
  which is critical for nonce cache, rate limiter, and session storage),
  Composer, and project dependencies if missing. Survives terminal
  resets/reboots.
- Enables apc.enable_cli=1 for console commands (matches Dockerfile)
- Subcommands: start, stop, status, restart
- Sets APP_SHARE_DIR to var/share for filesystem session persistence
- Clears dev cache on start

No database needed — preauth uses APCu + filesystem cache exclusively.

Port assignment: P-R-E = 7-7-3 → 8773
This commit was merged in pull request #6.
This commit is contained in:
2026-08-13 11:56:55 -04:00
parent 66b960ccea
commit c7585e720a
Executable
+265
View File
@@ -0,0 +1,265 @@
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────────────────
# PreAuth Dev Server Script
#
# Manages a local PHP dev server for end-to-end development and testing.
# Binds to 0.0.0.0 so the app is accessible via a reverse proxy (Caddy) for
# browser-based visual verification.
#
# PreAuth is a TOTP-based authentication gateway. It uses APCu for nonce/cache
# and filesystem for session persistence — no database needed. The dev server
# runs with APP_ENV=dev and APP_DEBUG=1 for live troubleshooting.
#
# Self-bootstrapping: the `start` command checks for required system packages
# (PHP, extensions, tools), Composer, and project dependencies — installing
# them automatically if missing. This means the script works even after a
# terminal reset/reboot, embracing the self-cleaning container design.
#
# Usage:
# bin/dev.sh start Start the dev server (auto-installs deps if needed)
# bin/dev.sh stop Stop the dev server
# bin/dev.sh status Check if the dev server is running
# bin/dev.sh restart Stop and start the dev server
#
# Port assignment (P-R-E = 7-7-3):
# 8773 → https://preauth.lyra-dev.devgnome.com
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail
# ── Configuration ───────────────────────────────────────────────────────────
PORT=8773
HOST="0.0.0.0"
ENV="dev"
DEV_SECRET="dev_secret_not_for_production_use_only"
PID_FILE="var/.dev-server.pid"
LOG_FILE="var/log/dev-server.log"
# Required PHP extensions (checked via php -m)
REQUIRED_PHP_EXTS=(
ctype
iconv
mbstring
apcu
dom
SimpleXML
xml
)
# Apt packages for PHP + extensions
# Note: preauth uses Symfony 7.4 which requires PHP >=8.1.
# We install PHP 8.4 (available in Debian 13/Trixie) for consistency.
PHP_APT_PACKAGES=(
php8.4-cli
php8.4-common # ctype, iconv
php8.4-mbstring
php8.4-xml # dom, SimpleXML, xml
php8.4-opcache
php8.4-readline
php8.4-apcu # APCu — critical for nonce cache, rate limiter, sessions
)
# System tools needed
SYSTEM_TOOLS=(
git
unzip
curl
)
# Resolve project root (script lives in bin/)
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$PROJECT_ROOT"
# Ensure var directory structure exists
mkdir -p var/log var/share
# ── Helpers ─────────────────────────────────────────────────────────────────
is_running() {
if [[ ! -f "$PID_FILE" ]]; then
return 1
fi
local pid
pid="$(cat "$PID_FILE")"
if [[ -z "$pid" ]] || ! kill -0 "$pid" 2>/dev/null; then
return 1
fi
return 0
}
print_status() {
if is_running; then
local pid
pid="$(cat "$PID_FILE")"
echo "✅ PreAuth dev server is RUNNING"
echo " PID: $pid"
echo " URL: http://localhost:${PORT}"
echo " Exposed: http://${HOST}:${PORT}"
echo " Dev URL: https://preauth.lyra-dev.devgnome.com"
echo " Logs: ${LOG_FILE}"
else
echo "⛔ PreAuth dev server is STOPPED"
fi
}
# ── Bootstrap ───────────────────────────────────────────────────────────────
# Ensures all system packages, Composer, and project dependencies are present.
# Idempotent — if everything is already installed, checks are fast no-ops.
# This is what makes the script survive terminal resets/reboots.
bootstrap() {
local needed_packages=()
# ── Check system tools ──
for tool in "${SYSTEM_TOOLS[@]}"; do
if ! command -v "$tool" &>/dev/null; then
needed_packages+=("$tool")
fi
done
# ── Check PHP and required extensions ──
local php_needs_install=false
if ! command -v php &>/dev/null; then
php_needs_install=true
else
for ext in "${REQUIRED_PHP_EXTS[@]}"; do
if ! php -m 2>/dev/null | grep -iq "^${ext}$"; then
php_needs_install=true
break
fi
done
fi
if [[ "$php_needs_install" == "true" ]]; then
needed_packages+=("${PHP_APT_PACKAGES[@]}")
fi
# ── Install missing packages ──
if [[ ${#needed_packages[@]} -gt 0 ]]; then
echo "→ Installing missing system packages: ${needed_packages[*]}"
sudo apt-get update -qq
sudo apt-get install -y -qq "${needed_packages[@]}"
fi
# ── Ensure APCu is enabled for CLI ──
# PreAuth's console commands need APCu; the Dockerfile sets apc.enable_cli=1
local apcu_ini="/etc/php/8.4/mods-available/apcu.ini"
if [[ -f "$apcu_ini" ]] && ! grep -q 'apc.enable_cli' "$apcu_ini" 2>/dev/null; then
echo "→ Enabling APCu CLI support…"
echo 'apc.enable_cli=1' | sudo tee -a "$apcu_ini" >/dev/null
fi
# ── Ensure Composer is available ──
if ! command -v composer &>/dev/null; then
echo "→ Installing Composer…"
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
sudo chmod +x /usr/local/bin/composer
fi
# ── Ensure project dependencies are installed ──
if [[ ! -d "vendor/" ]]; then
echo "→ Installing Composer dependencies…"
APP_ENV=dev composer install --no-interaction
fi
}
# ── Commands ────────────────────────────────────────────────────────────────
start() {
if is_running; then
echo "⚠️ Dev server is already running (PID $(cat "$PID_FILE"))"
print_status
exit 0
fi
echo "→ Starting PreAuth dev server on ${HOST}:${PORT}"
# Self-bootstrap: ensure all dependencies are present
bootstrap
echo "→ Clearing dev cache…"
APP_ENV="$ENV" \
APP_DEBUG=1 \
APP_SECRET="$DEV_SECRET" \
php bin/console cache:clear 2>&1 | tail -3
echo "→ Starting PHP dev server…"
APP_ENV="$ENV" \
APP_DEBUG=1 \
APP_SECRET="$DEV_SECRET" \
APP_SHARE_DIR="${PROJECT_ROOT}/var/share" \
nohup php -S "${HOST}:${PORT}" -t public/ > "$LOG_FILE" 2>&1 &
local pid=$!
echo "$pid" > "$PID_FILE"
# Give it a moment to boot
sleep 2
if is_running; then
echo ""
print_status
else
echo "❌ Failed to start dev server. Check logs:"
echo " ${LOG_FILE}"
tail -20 "$LOG_FILE" 2>/dev/null || true
rm -f "$PID_FILE"
exit 1
fi
}
stop() {
if ! is_running; then
echo "⚠️ Dev server is not running."
rm -f "$PID_FILE"
exit 0
fi
local pid
pid="$(cat "$PID_FILE")"
echo "→ Stopping dev server (PID ${pid})…"
kill "$pid" 2>/dev/null || true
# Wait for graceful shutdown
local count=0
while kill -0 "$pid" 2>/dev/null && [[ $count -lt 10 ]]; do
sleep 0.5
count=$((count + 1))
done
# Force kill if still alive
if kill -0 "$pid" 2>/dev/null; then
echo "→ Process didn't exit gracefully, sending SIGKILL…"
kill -9 "$pid" 2>/dev/null || true
fi
rm -f "$PID_FILE"
echo "✅ Dev server stopped."
}
restart() {
stop
sleep 1
start
}
# ── Main ────────────────────────────────────────────────────────────────────
usage() {
echo "Usage: bin/dev.sh {start|stop|status|restart}"
echo ""
echo "Commands:"
echo " start Start the dev server (auto-installs deps if needed)"
echo " stop Stop the dev server"
echo " status Check if the dev server is running"
echo " restart Restart the dev server"
exit 1
}
case "${1:-}" in
start) start ;;
stop) stop ;;
status) print_status ;;
restart) restart ;;
*) usage ;;
esac