#!/usr/bin/env bash # pyShelf installer # - Installs prerequisites # - Creates pyshelf user and directories # - Clones (or uses local tree) into /opt/pyshelf # - Installs uv for the pyshelf user # - uv sync + one-time DB init + static build # - Writes config # - Installs & starts systemd services set -euo pipefail REPO_URL="${REPO_URL:-https://github.com/th3r00t/pyShelf.git}" REPO_REF="${REPO_REF:-0.8.0--dev}" INSTALL_DIR="/opt/pyshelf" ETC_DIR="/etc/pyshelf" STATE_DIR="/var/lib/pyshelf" LOG_DIR="/var/log/pyshelf" BOOKPATH_DEFAULT="/mnt/books" USER="pyshelf" GROUP="pyshelf" PUBLIC_HOST="0.0.0.0" PORT="8080" MODE="git" # or "local" with --local if [[ "${1:-}" == "--local" ]]; then MODE="local"; fi need_cmd() { command -v "$1" >/dev/null 2>&1; } die() { echo "error: $*" >&2; exit 1; } #--- Detect distro & install prereqs ------------------------------------------------- install_prereqs() { if [[ -f /etc/arch-release ]]; then sudo pacman -Sy --noconfirm --needed git curl python python-pip gcc base-devel libxml2 libxslt libjpeg-turbo zlib elif [[ -f /etc/debian_version ]]; then sudo apt-get update sudo apt-get install -y git curl python3 python3-venv python3-dev build-essential libxml2-dev libxslt1-dev libjpeg-dev zlib1g-dev elif [[ -f /etc/fedora-release ]]; then sudo dnf install -y git curl python3 python3-devel gcc gcc-c++ libxml2-devel libxslt-devel libjpeg-turbo-devel zlib-devel else die "Unsupported distro. Add your package steps here." fi } #--- Create user, dirs, config ------------------------------------------------------ setup_user_dirs() { if ! id -u "$USER" >/dev/null 2>&1; then sudo useradd --system --create-home --home-dir "$STATE_DIR" "$USER" fi sudo mkdir -p "$ETC_DIR" "$STATE_DIR" "$LOG_DIR" "$BOOKPATH_DEFAULT" # Try to add group if missing if ! getent group "$GROUP" >/dev/null 2>&1; then sudo groupadd -f "$GROUP" || true sudo usermod -g "$GROUP" "$USER" || true fi sudo chown -R "$USER":"$GROUP" "$STATE_DIR" "$LOG_DIR" || sudo chown -R "$USER":"$USER" "$STATE_DIR" "$LOG_DIR" sudo chown -R "$USER":"$GROUP" "$BOOKPATH_DEFAULT" || true # default config sudo tee "$ETC_DIR/config.json" >/dev/null < /opt/pyshelf sudo rm -rf "$INSTALL_DIR" sudo mkdir -p "$INSTALL_DIR" sudo cp -a . "$INSTALL_DIR" cd "$INSTALL_DIR" fi sudo chown -R "$USER":"$GROUP" "$INSTALL_DIR" || true } #--- Install uv for the pyshelf user ------------------------------------------------ install_uv_for_user() { # Install uv into /home/pyshelf/.local/bin sudo -u "$USER" sh -lc 'command -v uv >/dev/null 2>&1 || (curl -LsSf https://astral.sh/uv/install.sh | sh)' sudo -u "$USER" sh -lc 'uv --version' || die "uv not available for $USER" } #--- Python deps (uv) --------------------------------------------------------------- sync_python() { # Ensure PYTHONPATH=src so src/ layout packages (frontend, backend) import sudo -u "$USER" sh -lc "cd '$INSTALL_DIR' && PYTHONPATH=src uv sync" } #--- One-time prework: create DB & compile static ----------------------------------- prework() { # Create DB/tables sudo -u "$USER" sh -lc "cd '$INSTALL_DIR' && PYTHONPATH=src uv run python - <<'PY' from backend.lib.config import Config from backend.lib.storage import Storage import os Storage(Config(os.getcwd())).create_tables() print('DB initialized.') PY" # Compile SASS directly (avoid importing your app module) sudo -u "$USER" sh -lc "cd '$INSTALL_DIR' && PYTHONPATH=src uv run python - <<'PY' import sass, os, sys from pathlib import Path sass_in = 'src/frontend/static/styles/pyShelf.sass' css_out = 'src/frontend/static/styles/pyShelf.css' Path(os.path.dirname(css_out)).mkdir(parents=True, exist_ok=True) compiled, _map = sass.compile( filename=sass_in, source_map_filename=sass_in, output_style='compressed', include_paths=['node_modules','src/frontend/static/styles'] ) with open(css_out, 'w') as f: f.write(compiled) print('Static assets compiled.') PY" } #--- Systemd units ------------------------------------------------------------------ install_systemd() { # Service: runs uvicorn against FastAPI app object sudo tee /etc/systemd/system/pyshelf.service >/dev/null </dev/null </dev/null <<'UNIT' [Unit] Description=Run pyShelf scan nightly [Timer] OnCalendar=03:30 Persistent=true Unit=pyshelf-scan.service [Install] WantedBy=timers.target UNIT sudo systemctl daemon-reload sudo systemctl enable --now pyshelf.service sudo systemctl enable --now pyshelf-scan.timer } main() { install_prereqs setup_user_dirs fetch_source install_uv_for_user ensure_runtime_dirs sync_python prework install_systemd echo echo "pyShelf installed." echo " URL: http://$PUBLIC_HOST:$PORT/" echo " Config: $ETC_DIR/config.json" echo " Books: $BOOKPATH_DEFAULT" echo " Repo: $INSTALL_DIR" echo " Logs: $LOG_DIR" } main "$@"