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%
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