#!/usr/bin/env bash set -euo pipefail script_path=${BASH_SOURCE[0]} if [[ "$script_path" == */* ]]; then script_dir=${script_path%/*} else script_dir=. fi cd "$script_dir/.." root_dir=$PWD if [[ -n "${CONTAINER_ENGINE:-}" ]]; then engine=$CONTAINER_ENGINE elif command -v podman >/dev/null 2>&1; then engine=podman elif command -v docker >/dev/null 2>&1; then engine=docker else printf 'Need podman or docker in PATH.\n' >&2 exit 1 fi if ! command -v "$engine" >/dev/null 2>&1; then printf 'Container engine not found: %s\n' "$engine" >&2 exit 1 fi image=${FILES_DEV_IMAGE:-files-dev-nextjs:latest} container=${FILES_DEV_CONTAINER:-files-dev-nextjs} host_port=${FILES_DEV_PORT:-3000} dev_dir=${FILES_DEV_DIR:-$root_dir/.dev} app_data_dir=$dev_dir/app-data storage_dir=$dev_dir/storage password_hash='$2a$12$8d4a4zH/tb5TYiP4wQpuFO4GheZarYDWVDGl2S90R4xdMd83JOPMm' volume_arg() { if [[ "$engine" == podman ]]; then printf '%s:%s:Z' "$1" "$2" else printf '%s:%s' "$1" "$2" fi } mkdir -p "$app_data_dir" "$storage_dir/_share" printf 'Using %s\n' "$engine" printf 'Building %s\n' "$image" "$engine" build -t "$image" "$root_dir/nextjs" printf 'Seeding dev user john.doe / 123456\n' "$engine" run --rm -i \ -e DB_PATH=/app/data/uploads.sqlite \ -e DEV_PASSWORD_HASH="$password_hash" \ -v "$(volume_arg "$app_data_dir" /app/data)" \ -v "$(volume_arg "$storage_dir" /data)" \ "$image" node - <<'NODE' const fs = require('node:fs'); const path = require('node:path'); const sqlite3 = require('sqlite3'); const dbPath = process.env.DB_PATH || '/app/data/uploads.sqlite'; const passwordHash = process.env.DEV_PASSWORD_HASH; fs.mkdirSync(path.dirname(dbPath), { recursive: true }); fs.mkdirSync('/data/_share', { recursive: true }); const db = new sqlite3.Database(dbPath); function run(sql, params = []) { return new Promise((resolve, reject) => { db.run(sql, params, function onRun(error) { if (error) { reject(error); return; } resolve(this); }); }); } function close() { return new Promise((resolve, reject) => { db.close((error) => { if (error) { reject(error); return; } resolve(); }); }); } (async () => { await run(`CREATE TABLE IF NOT EXISTS users ( username TEXT PRIMARY KEY, password_hash TEXT NOT NULL, created_at INTEGER NOT NULL, is_admin INTEGER NOT NULL DEFAULT 0 )`); await run('ALTER TABLE users ADD COLUMN is_admin INTEGER NOT NULL DEFAULT 0').catch(() => undefined); await run( `INSERT INTO users (username, password_hash, created_at, is_admin) VALUES (?, ?, ?, 1) ON CONFLICT(username) DO UPDATE SET password_hash = excluded.password_hash, is_admin = 1`, ['john.doe', passwordHash, Date.now()] ); await close(); })().catch(async (error) => { try { await close(); } catch { } console.error(error); process.exit(1); }); NODE if "$engine" container inspect "$container" >/dev/null 2>&1; then printf 'Removing old %s\n' "$container" "$engine" rm -f "$container" >/dev/null fi logs_pid= watchdog_pid= stop_watchdog() { if [[ -n "$watchdog_pid" ]]; then kill "$watchdog_pid" >/dev/null 2>&1 || true wait "$watchdog_pid" >/dev/null 2>&1 || true watchdog_pid= fi } cleanup() { status=$? trap - EXIT INT TERM HUP QUIT stop_watchdog if [[ -n "$logs_pid" ]]; then kill "$logs_pid" >/dev/null 2>&1 || true wait "$logs_pid" >/dev/null 2>&1 || true fi if "$engine" container inspect "$container" >/dev/null 2>&1; then printf '\nKilling %s\n' "$container" >&2 "$engine" rm -f "$container" >/dev/null 2>&1 || true fi exit "$status" } start_watchdog() { local parent_pid=$$ ( trap '' INT TERM HUP QUIT while kill -0 "$parent_pid" >/dev/null 2>&1; do sleep 0.2 done "$engine" rm -f "$container" >/dev/null 2>&1 || true ) & watchdog_pid=$! } trap cleanup EXIT trap 'exit 130' INT trap 'exit 143' TERM trap 'exit 129' HUP trap 'exit 131' QUIT printf '\nPreview: http://localhost:%s\n' "$host_port" printf 'User login: john.doe / 123456\n' printf 'Admin area: /manage/admin, password 123456\n' printf 'Data dir: %s\n' "$dev_dir" printf 'Stop: Ctrl-C\n\n' "$engine" run --rm -d \ --name "$container" \ --stop-timeout 1 \ -p "127.0.0.1:$host_port:3000" \ -e DATA_DIR=/data \ -e DB_PATH=/app/data/uploads.sqlite \ -e SERVICE_FQDN=localhost \ -e PUBLIC_BASE_URL="http://localhost:$host_port" \ -e UPLOAD_TTL_SECONDS=604800 \ -e UPLOAD_MAX_BYTES=26843545600 \ -e MANAGEMENT_ADMIN_HASH="$password_hash" \ -e COOKIE_SECURE=false \ -e SMTP_HOST= \ -e SMTP_PORT=587 \ -e SMTP_USER= \ -e SMTP_PASS= \ -e SMTP_MAIL= \ -e SMTP_NAME= \ -e PORT=3000 \ -v "$(volume_arg "$app_data_dir" /app/data)" \ -v "$(volume_arg "$storage_dir" /data)" \ "$image" >/dev/null start_watchdog "$engine" logs -f "$container" & logs_pid=$! set +e container_status=$("$engine" wait "$container") wait_status=$? set -e kill "$logs_pid" >/dev/null 2>&1 || true wait "$logs_pid" >/dev/null 2>&1 || true logs_pid= stop_watchdog trap - EXIT INT TERM HUP QUIT if [[ $wait_status -ne 0 ]]; then exit "$wait_status" fi if [[ "$container_status" =~ ^[0-9]+$ ]]; then exit "$container_status" fi