#!/usr/bin/env bash set -euo pipefail PORT="9000" USER="" PASS="" DOCROOT="" MEDIA_ROOT_ARG="" MEDIA_CACHE_DIR_ARG="" MEDIA_CACHE_INTERVAL_ARG="" read_pass_stdin=false prompt=false usage() { cat <<'EOF' Usage: start-media-server.sh --user [--pass | --pass-stdin | --prompt] [--media-root ] [--cache-dir ] [--cache-interval ] [--port ] [--dir ] Options: --user Username to set in MEDIA_USER (required) --pass

Password to hash (insecure: visible in process list & shell history) --pass-stdin Read password from stdin (safer) --prompt Prompt for password (safer; hidden input) --media-root

Base directory containing Videos/ and Music/ (recommended) --cache-dir

Directory where media cache JSON and lock files are stored --cache-interval Cache rebuild interval in seconds (default: 900) --port

Listen port (default: 9000) --dir cd into docroot before starting (optional) -h, --help Show help Examples: ./start-media-server.sh --user admin --prompt --media-root /mnt/media --cache-dir var/cache/media --port 9000 --dir /home/me/samba-serv printf '%s\n' 'test123' | ./start-media-server.sh --user admin --pass-stdin --media-root /mnt/media EOF } have_cmd() { command -v "$1" >/dev/null 2>&1; } pick_php_runner() { if have_cmd php; then echo "php" return 0 fi if have_cmd frankenphp; then if frankenphp -v >/dev/null 2>&1; then echo "frankenphp php-cli" return 0 fi fi return 1 } while (($#)); do case "$1" in --user) USER="${2-}"; shift 2 ;; --pass) PASS="${2-}"; shift 2 ;; --pass-stdin) read_pass_stdin=true; shift ;; --prompt) prompt=true; shift ;; --media-root) MEDIA_ROOT_ARG="${2-}"; shift 2 ;; --cache-dir) MEDIA_CACHE_DIR_ARG="${2-}"; shift 2 ;; --cache-interval) MEDIA_CACHE_INTERVAL_ARG="${2-}"; shift 2 ;; --port) PORT="${2-}"; shift 2 ;; --dir) DOCROOT="${2-}"; shift 2 ;; -h|--help) usage; exit 0 ;; *) echo "Unknown argument: $1" >&2 usage exit 2 ;; esac done if [[ -z "$USER" ]]; then echo "Error: --user is required" >&2 usage exit 2 fi if $read_pass_stdin && $prompt; then echo "Error: choose only one of --pass-stdin or --prompt" >&2 exit 2 fi if [[ -z "$PASS" ]]; then if $read_pass_stdin; then IFS= read -r PASS elif $prompt; then read -r -s -p "Password: " PASS echo else echo "Error: provide --pass, --pass-stdin, or --prompt" >&2 usage exit 2 fi fi if ! [[ "$PORT" =~ ^[0-9]+$ ]] || ((PORT < 1 || PORT > 65535)); then echo "Error: invalid --port '$PORT'" >&2 exit 2 fi if [[ -n "$MEDIA_CACHE_INTERVAL_ARG" ]]; then if ! [[ "$MEDIA_CACHE_INTERVAL_ARG" =~ ^[0-9]+$ ]] || ((MEDIA_CACHE_INTERVAL_ARG < 1)); then echo "Error: invalid --cache-interval '$MEDIA_CACHE_INTERVAL_ARG'" >&2 exit 2 fi fi if ! have_cmd frankenphp; then echo "Error: frankenphp not found in PATH" >&2 exit 127 fi PHP_RUNNER="$(pick_php_runner)" || { echo "Error: neither 'php' nor 'frankenphp php-cli' is available for hashing" >&2 exit 127 } # Generate bcrypt hash without needing to escape $. MEDIA_PASS_HASH="$( P="$PASS" $PHP_RUNNER -r 'echo password_hash(getenv("P"), PASSWORD_BCRYPT), PHP_EOL;' )" export MEDIA_USER="$USER" export MEDIA_PASS_HASH # Export MEDIA_ROOT if provided if [[ -n "${MEDIA_ROOT_ARG}" ]]; then export MEDIA_ROOT="${MEDIA_ROOT_ARG}" fi if [[ -n "${MEDIA_CACHE_DIR_ARG}" ]]; then export MEDIA_CACHE_DIR="${MEDIA_CACHE_DIR_ARG}" fi if [[ -n "${MEDIA_CACHE_INTERVAL_ARG}" ]]; then export MEDIA_CACHE_INTERVAL="${MEDIA_CACHE_INTERVAL_ARG}" fi if [[ -n "$DOCROOT" ]]; then cd "$DOCROOT" fi echo "Using PHP runner: $PHP_RUNNER" echo "Starting frankenphp on :$PORT" echo "MEDIA_USER=$MEDIA_USER" echo "MEDIA_PASS_HASH set (bcrypt)" if [[ -n "${MEDIA_ROOT:-}" ]]; then echo "MEDIA_ROOT=$MEDIA_ROOT" else echo "MEDIA_ROOT not set (get_files.php / serve_media.php will error)" fi if [[ -n "${MEDIA_CACHE_DIR:-}" ]]; then echo "MEDIA_CACHE_DIR=$MEDIA_CACHE_DIR" else echo "MEDIA_CACHE_DIR=$(pwd)/var/cache/media" fi echo "MEDIA_CACHE_INTERVAL=${MEDIA_CACHE_INTERVAL:-900}s" echo run_cache_builder() { if $PHP_RUNNER "$(pwd)/build_media_cache.php"; then return 0 fi echo "Warning: media cache build failed" >&2 return 1 } cache_loop() { local interval="${MEDIA_CACHE_INTERVAL:-900}" while true; do run_cache_builder || true sleep "$interval" done } cleanup() { if [[ -n "${CACHE_LOOP_PID:-}" ]]; then kill "${CACHE_LOOP_PID}" >/dev/null 2>&1 || true wait "${CACHE_LOOP_PID}" >/dev/null 2>&1 || true fi if [[ -n "${SERVER_PID:-}" ]]; then kill "${SERVER_PID}" >/dev/null 2>&1 || true fi } run_cache_builder || true cache_loop & CACHE_LOOP_PID=$! trap cleanup EXIT INT TERM frankenphp php-server --listen ":$PORT" & SERVER_PID=$! wait "$SERVER_PID"