#!/usr/bin/env bash # stemdeck dev server control: setup | start | stop | restart | status set -euo pipefail cd "$(dirname "$0")" HOST="${HOST:-0.0.0.0}" PORT="${PORT:-8000}" RELOAD="${RELOAD:-0}" # Treat the self-hosted server as a persistent, user-managed library (like the # desktop app): opt out of the 24h job TTL sweep so processed tracks are not # auto-deleted. Override with STEMDECK_PERSIST_LIBRARY=0 for disk-hygiene mode. export STEMDECK_PERSIST_LIBRARY="${STEMDECK_PERSIST_LIBRARY:-1}" FOREGROUND="${FOREGROUND:-0}" PID_FILE=".run/uvicorn.pid" LOG_FILE=".run/uvicorn.log" UVICORN=".venv/bin/uvicorn" mkdir -p .run is_running() { [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null } start() { if is_running; then echo "already running (pid $(cat "$PID_FILE"))" return 0 fi if [[ ! -x "$UVICORN" ]]; then echo "uvicorn not found at $UVICORN — run: uv sync" >&2 exit 1 fi echo "starting on http://$HOST:$PORT" local args=(app.main:app --host "$HOST" --port "$PORT") if [[ "$RELOAD" == "1" ]]; then args+=(--reload) fi if [[ "$FOREGROUND" == "1" ]]; then echo $$ >"$PID_FILE" exec "$UVICORN" "${args[@]}" fi nohup "$UVICORN" "${args[@]}" >"$LOG_FILE" 2>&1 & echo $! >"$PID_FILE" for _ in {1..10}; do sleep 0.5 grep -q "Application startup complete\|Uvicorn running on" "$LOG_FILE" 2>/dev/null && break is_running || break done if is_running; then echo "started (pid $(cat "$PID_FILE"), log: $LOG_FILE)" else echo "failed to start — see $LOG_FILE" >&2 exit 1 fi } stop() { if ! is_running; then echo "not running" # also sweep any stray uvicorn for this app pkill -f "uvicorn app.main:app" 2>/dev/null || true rm -f "$PID_FILE" return 0 fi local pid pid=$(cat "$PID_FILE") echo "stopping pid $pid" kill "$pid" 2>/dev/null || true for _ in {1..10}; do kill -0 "$pid" 2>/dev/null || break sleep 0.5 done if kill -0 "$pid" 2>/dev/null; then echo "force-killing pid $pid" kill -9 "$pid" 2>/dev/null || true fi # kill any in-flight demucs children spawned by the app pkill -f "python -m demucs" 2>/dev/null || true rm -f "$PID_FILE" echo "stopped" } status() { if is_running; then echo "running (pid $(cat "$PID_FILE")) on http://$HOST:$PORT" else echo "not running" fi } setup() { local os case "$(uname -s)" in Darwin) os="macos" ;; Linux) os="linux" ;; *) echo "setup: unsupported OS '$(uname -s)' — install ffmpeg + uv manually" >&2 exit 1 ;; esac echo "detected: $os" if [[ "$os" == "macos" ]]; then if ! command -v brew >/dev/null 2>&1; then echo "setup: Homebrew not found — install from https://brew.sh first" >&2 exit 1 fi if ! command -v ffmpeg >/dev/null 2>&1; then echo "==> brew install ffmpeg" brew install ffmpeg else echo "ffmpeg: already installed ($(ffmpeg -version | head -n1))" fi if ! command -v uv >/dev/null 2>&1; then echo "==> brew install uv" brew install uv else echo "uv: already installed ($(uv --version))" fi else # linux: assume Debian/Ubuntu (apt). Other distros fall through to manual. if ! command -v apt-get >/dev/null 2>&1; then echo "setup: non-apt Linux detected — install ffmpeg + uv via your package manager" >&2 exit 1 fi if ! command -v ffmpeg >/dev/null 2>&1; then echo "==> sudo apt-get update && sudo apt-get install -y ffmpeg" sudo apt-get update sudo apt-get install -y ffmpeg else echo "ffmpeg: already installed ($(ffmpeg -version | head -n1))" fi if ! command -v uv >/dev/null 2>&1; then echo "==> installing uv via astral.sh installer" curl -LsSf https://astral.sh/uv/install.sh | sh # installer drops uv in ~/.local/bin; surface it for this shell export PATH="$HOME/.local/bin:$PATH" else echo "uv: already installed ($(uv --version))" fi fi echo "==> uv sync" uv sync --python 3.12 echo echo "setup complete. start the server with: ./run.sh start" } case "${1:-}" in setup) setup ;; start) start ;; stop) stop ;; restart) stop; start ;; status) status ;; *) echo "usage: $0 {setup|start|stop|restart|status}" >&2 exit 2 ;; esac