SPB Git

spb/drive Public

SPB Drive — self-hosted personal cloud drive (files, previews, sharing) on the MacLustr cluster.

JavaScript 82.7% CSS 10.6% Nunjucks 3.6% Shell 1.8% SQL 1.3%
5.6 KB · 120 lines shellscript
Raw Blame History
1#!/usr/bin/env bash2# ─────────────────────────────────────────────3#  SPB Drive — Personal Cloud Drive4# ─────────────────────────────────────────────5#  Author  : Simon-Pierre Boucher6#  Contact : contact@spboucher.ai7#  File    : deploy/setup-m3u96b.sh8#  Purpose : Idempotent bootstrap of SPB Drive on node m3u96b (macOS)9#  License : MIT © Simon-Pierre Boucher10# ─────────────────────────────────────────────11#12# Usage (on m3u96b, from anywhere):13#   SPBDRIVE_BOOTSTRAP_PASSWORD=… bash deploy/setup-m3u96b.sh [path-to-repo]14# The repo is rsynced (or already present) at /srv/drive/app.15# Re-running is always safe.1617set -euo pipefail1819# macOS seals /srv (read-only system volume) — the drive lives under $HOME/srv.20DATA_DIR="$HOME/srv/drive"21APP_DIR="$DATA_DIR/app"22REPO_SRC="${1:-$PWD}"23BREW="$(command -v brew || echo /opt/homebrew/bin/brew)"2425log() { printf '\033[1;34m▸ %s\033[0m\n' "$*"; }2627# 1 ── System packages ──────────────────────────────────────────────────28log "Installing system packages (ffmpeg, libreoffice, poppler, 7z, ngrok, pm2)"29"$BREW" list ffmpeg >/dev/null 2>&1 || "$BREW" install ffmpeg30"$BREW" list poppler >/dev/null 2>&1 || "$BREW" install poppler        # pdftotext + pdftoppm31"$BREW" list sevenzip >/dev/null 2>&1 || "$BREW" install sevenzip      # 7z32if [ ! -d /Applications/LibreOffice.app ]; then33  # macOS 26 blocks headless hdiutil attach with a consent prompt, so the cask34  # can't install over SSH. Fetch the DMG via brew, extract it with 7zz instead.35  "$BREW" fetch --cask libreoffice >/dev/null || true36  LO_DMG="$(ls "$HOME"/Library/Caches/Homebrew/downloads/*LibreOffice*.dmg 2>/dev/null | head -1)"37  if [ -n "$LO_DMG" ]; then38    LO_TMP="$(mktemp -d)"39    (cd "$LO_TMP" && 7zz x -y "$LO_DMG" >/dev/null 2>&1) || true40    LO_APP="$(find "$LO_TMP" -maxdepth 3 -name 'LibreOffice.app' | head -1)"41    if [ -n "$LO_APP" ]; then42      ditto "$LO_APP" /Applications/LibreOffice.app43      xattr -dr com.apple.quarantine /Applications/LibreOffice.app 2>/dev/null || true44    fi45    rm -rf "$LO_TMP"46  fi47  [ -d /Applications/LibreOffice.app ] || \48    echo "⚠ LibreOffice install failed — office previews will fall back to download cards" >&249fi50command -v ngrok >/dev/null 2>&1 || "$BREW" install ngrok51command -v node >/dev/null 2>&1 || "$BREW" install node@2052command -v pm2 >/dev/null 2>&1 || npm install -g pm25354NODE_MAJOR="$(node -p 'process.versions.node.split(".")[0]')"55if [ "$NODE_MAJOR" -lt 20 ]; then56  echo "Node >= 20 required (found $(node -v))" >&257  exit 158fi5960# 2 ── Data directories (700) ───────────────────────────────────────────61log "Creating $DATA_DIR layout"62mkdir -p "$DATA_DIR"/{files,db,cache,backups,logs}63chmod 700 "$DATA_DIR" "$DATA_DIR"/{files,db,cache,backups,logs}6465# 3 ── App code + dependencies ──────────────────────────────────────────66log "Syncing app to $APP_DIR"67mkdir -p "$APP_DIR"68if [ "$REPO_SRC" != "$APP_DIR" ]; then69  rsync -a --delete \70    --exclude node_modules --exclude data --exclude .git --exclude .DS_Store \71    "$REPO_SRC"/ "$APP_DIR"/72fi73cd "$APP_DIR"74log "Installing production dependencies"75npm ci --omit=dev --no-audit --no-fund7677# 4 ── First-boot password + keys ───────────────────────────────────────78if [ ! -f "$DATA_DIR/auth.json" ]; then79  if [ -z "${SPBDRIVE_BOOTSTRAP_PASSWORD:-}" ]; then80    printf 'First boot — choose the drive password: '81    read -rs SPBDRIVE_BOOTSTRAP_PASSWORD82    echo83    export SPBDRIVE_BOOTSTRAP_PASSWORD84  fi85  log "Seeding auth.json + keys.json (argon2id, chmod 600)"86  SPBDRIVE_DATA_DIR="$DATA_DIR" node -e '87    import("./src/config.mjs").then(async ({ ensureDataDirs }) => {88      ensureDataDirs();89      const { ensureAuthBootstrap } = await import("./src/auth/password.mjs");90      await ensureAuthBootstrap();91      console.log("auth seeded");92    });93  '94else95  log "auth.json already present — keeping existing password"96fi9798# 5 ── pm2: server + tunnel, boot persistence ──────────────────────────99log "Starting pm2 apps"100pm2 startOrReload deploy/ecosystem.config.cjs101pm2 save102if ! pm2 startup 2>/dev/null | grep -q 'already'; then103  log "If pm2 printed a startup command above, run it once with sudo so the drive survives reboots."104fi105106# 6 ── Health check ─────────────────────────────────────────────────────107sleep 3108if curl -fsS http://127.0.0.1:7430/healthz >/dev/null; then109  log "✓ SPB Drive is healthy on :7430 → https://drive.spboucher.ai"110else111  echo "✗ healthz failed — check: pm2 logs spbdrive-server" >&2112  exit 1113fi114115# 7 ── Nightly backup cron (02:30) ──────────────────────────────────────116log "Installing nightly backup cron"117CRON_LINE="30 2 * * * /bin/bash $APP_DIR/deploy/backup.sh >> $DATA_DIR/logs/backup.log 2>&1"118( crontab -l 2>/dev/null | grep -v 'deploy/backup.sh' ; echo "$CRON_LINE" ) | crontab -119log "Done. Reserve drive.spboucher.ai in the ngrok dashboard + DNS CNAME if not already done."120