From c7585e720a8b80f3bdfbb05319062ae3843d263c Mon Sep 17 00:00:00 2001 From: Lyra Bot Date: Thu, 13 Aug 2026 11:56:55 -0400 Subject: [PATCH] feat: add self-bootstrapping dev server script (bin/dev.sh) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- bin/dev.sh | 265 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100755 bin/dev.sh diff --git a/bin/dev.sh b/bin/dev.sh new file mode 100755 index 0000000..a3fe03c --- /dev/null +++ b/bin/dev.sh @@ -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