#!/usr/bin/env bash # Local development orchestration. Run inside `nix develop`. set -euo pipefail ROOT="${DEV_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" FRONTEND="$ROOT/think.greaterchiangmai.com" BACKEND="$ROOT/think-backend.greaterchiangmai.com" STATE="$ROOT/.dev" MYSQL_DATA="$STATE/mysql" MYSQL_SOCK="$STATE/mysql.sock" MYSQL_PID="$STATE/mysql.pid" MYSQL_LOG="$STATE/mysql.log" # Production export (phpMyAdmin .sql, or the .zip it came in). Seeded into every fresh DB when present. DB_DUMP="${DEV_DB_DUMP:-$STATE/greaterc_db2024.sql}" # Must match DB_* in both apps' .env (only the port differs, to avoid clashing with a system MySQL). DB_PORT="${DEV_DB_PORT:-3307}" DB_NAME="greaterc_db2024" DB_USER="greaterc_dbname2024" DB_PASS="6vK25y6Pz8LA6Z3y78qH" FRONTEND_PORT="${DEV_FRONTEND_PORT:-8000}" BACKEND_PORT="${DEV_BACKEND_PORT:-8001}" # Process env beats .env in Laravel, so this redirects both apps to the local DB without editing .env. export DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT="$DB_PORT" DB_DATABASE="$DB_NAME" DB_USERNAME="$DB_USER" DB_PASSWORD="$DB_PASS" export APP_ENV=local APP_DEBUG=true TEST_LOCAL=true usage() { cat < setup composer install both apps + init local MariaDB db start|stop|status manage local MariaDB (data in .dev/mysql, port $DB_PORT); start runs backend migrations db shell mysql client on the local DB db import drop + recreate the DB from a production dump (schema + data) db reset drop + recreate the DB, seed from .dev/greaterc_db2024.sql[.zip] if present, migrate serve frontend php artisan serve -> http://127.0.0.1:$FRONTEND_PORT serve backend php artisan serve -> http://127.0.0.1:$BACKEND_PORT up db + both apps; Ctrl-C stops everything down stop db and any background servers artisan ... run artisan for frontend|backend with local env EOF } app_dir() { case "$1" in frontend) echo "$FRONTEND" ;; backend) echo "$BACKEND" ;; *) echo "unknown app: $1 (frontend|backend)" >&2; exit 2 ;; esac } db_running() { [ -f "$MYSQL_PID" ] && kill -0 "$(cat "$MYSQL_PID")" 2>/dev/null } db_init() { [ -d "$MYSQL_DATA" ] && return mkdir -p "$MYSQL_DATA" mariadb-install-db --datadir="$MYSQL_DATA" --auth-root-authentication-method=normal >"$STATE/install.log" 2>&1 } db_start() { mkdir -p "$STATE" db_init if db_running; then echo "db: already running (pid $(cat "$MYSQL_PID"))" db_migrate return fi # Ignore terminal Ctrl-C in the DB so `up` can shut it down cleanly after the app servers. (trap '' INT; exec mariadbd --datadir="$MYSQL_DATA" --socket="$MYSQL_SOCK" --pid-file="$MYSQL_PID" \ --port="$DB_PORT" --bind-address=127.0.0.1 --skip-name-resolve) \ >"$MYSQL_LOG" 2>&1 & for _ in $(seq 1 50); do mariadb-admin --socket="$MYSQL_SOCK" -uroot ping >/dev/null 2>&1 && break sleep 0.2 done mariadb-admin --socket="$MYSQL_SOCK" -uroot ping >/dev/null 2>&1 || { echo "db: failed to start, see $MYSQL_LOG" >&2; exit 1; } mariadb --socket="$MYSQL_SOCK" -uroot </dev/null; then db_recreate db_seed fi db_migrate echo "db: listening on 127.0.0.1:$DB_PORT (pid $(cat "$MYSQL_PID"))" } # The schema lives in think-backend/database/migrations; `migrate` is a no-op once applied. db_migrate() { [ -f "$BACKEND/vendor/autoload.php" ] || composer_install (cd "$BACKEND" && php artisan migrate --force --no-interaction) } db_recreate() { mariadb --socket="$MYSQL_SOCK" -uroot -e "DROP DATABASE IF EXISTS \`$DB_NAME\`; CREATE DATABASE \`$DB_NAME\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" } # Load the production export into the (empty) DB. Unzips once if only the archive is present. db_seed() { if [ ! -f "$DB_DUMP" ] && [ -f "$DB_DUMP.zip" ]; then unzip -o -q "$DB_DUMP.zip" "$(basename "$DB_DUMP")" -d "$(dirname "$DB_DUMP")" fi if [ ! -f "$DB_DUMP" ]; then echo "db: no dump at $DB_DUMP, starting with empty tables" return fi mariadb --socket="$MYSQL_SOCK" -uroot "$DB_NAME" <"$DB_DUMP" echo "db: seeded from $DB_DUMP" } db_stop() { db_running || { echo "db: not running"; return; } mariadb-admin --socket="$MYSQL_SOCK" -uroot shutdown echo "db: stopped" } db_status() { if db_running; then echo "db: running on 127.0.0.1:$DB_PORT (pid $(cat "$MYSQL_PID"))" mariadb --socket="$MYSQL_SOCK" -uroot "$DB_NAME" -e "SHOW TABLES" else echo "db: stopped" fi } # phpMyAdmin dumps carry bare CREATE TABLEs, so start from an empty database. db_import() { local dump="${1:?usage: dev db import }" [ -f "$dump" ] || { echo "db: no such file: $dump" >&2; exit 1; } db_running || db_start db_recreate mariadb --socket="$MYSQL_SOCK" -uroot "$DB_NAME" <"$dump" echo "db: imported $dump" mariadb --socket="$MYSQL_SOCK" -uroot "$DB_NAME" -e "SHOW TABLES" } db_reset() { db_running || db_start db_recreate db_seed db_migrate echo "db: reset" } composer_install() { for app in "$FRONTEND" "$BACKEND"; do [ -f "$app/.env" ] || { echo "missing $app/.env" >&2; exit 1; } (cd "$app" && composer install --no-interaction) done } serve() { local dir port dir="$(app_dir "$1")" case "$1" in frontend) port="$FRONTEND_PORT" ;; backend) port="$BACKEND_PORT" ;; esac cd "$dir" && exec php artisan serve --no-reload --host=127.0.0.1 --port="$port" } up() { for app in "$FRONTEND" "$BACKEND"; do [ -f "$app/vendor/autoload.php" ] || composer_install done db_start local pids=() cleanup() { trap - EXIT INT TERM set +e kill "${pids[@]}" 2>/dev/null wait "${pids[@]}" 2>/dev/null db_stop } trap cleanup EXIT INT TERM # --no-reload: without it `artisan serve` drops our DB_*/APP_* overrides from the php -S child env. (cd "$FRONTEND" && php artisan serve --no-reload --host=127.0.0.1 --port="$FRONTEND_PORT") & pids+=($!) (cd "$BACKEND" && php artisan serve --no-reload --host=127.0.0.1 --port="$BACKEND_PORT") & pids+=($!) echo "frontend: http://127.0.0.1:$FRONTEND_PORT" echo "backend: http://127.0.0.1:$BACKEND_PORT" wait } down() { pkill -f "artisan serve --no-reload --host=127.0.0.1 --port=($FRONTEND_PORT|$BACKEND_PORT)" 2>/dev/null || true db_stop } cmd="${1:-help}"; shift || true case "$cmd" in setup) composer_install; db_start ;; db) sub="${1:-status}"; shift || true case "$sub" in start) db_start ;; stop) db_stop ;; status) db_status ;; shell) db_running || db_start; exec mariadb --socket="$MYSQL_SOCK" -uroot "$DB_NAME" ;; reset) db_reset ;; import) db_import "$@" ;; *) usage; exit 2 ;; esac ;; serve) serve "${1:?usage: dev serve frontend|backend}" ;; up) up ;; down) down ;; artisan) dir="$(app_dir "${1:?usage: dev artisan frontend|backend ...}")"; shift; cd "$dir" && exec php artisan "$@" ;; help|-h|--help) usage ;; *) usage; exit 2 ;; esac