Files
periscope/scripts/update-periscope.sh
T
michele c362ef8a56 Split native Periscope (periscope/src) from inherited PinScope (periscope/dependency).
Keep validate.py and the finding engine as-is. AGPL LICENSE stays at the repo root.
Docker overlays dependency then src. Do not delete the inherited tree.
2026-09-20 14:31:42 +02:00

217 lines
6.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# Rebuild and restart the Periscope stack on the production host
# (periscope.michelebigi.it).
#
# Canonical live checkout (one tree only):
# /root/periscope
# Clone URL may still be github.com/manvalan/pinscope — clone into that path:
# git clone git@github.com:manvalan/pinscope.git /root/periscope
#
# Resolve the repo root from this script's location only (no find):
# cd /root/periscope
# ./scripts/update-periscope.sh
# or:
# /root/periscope/scripts/update-periscope.sh
#
# Optional:
# SITE=https://periscope.michelebigi.it ./scripts/update-periscope.sh
# ./scripts/update-periscope.sh --no-pull
# CANONICAL_ROOT=/other/periscope ./scripts/update-periscope.sh
#
# Does not touch ./data (projects + component library).
set -euo pipefail
SITE="${SITE:-https://periscope.michelebigi.it}"
CANONICAL_ROOT="${CANONICAL_ROOT:-/root/periscope}"
DO_PULL=1
for arg in "$@"; do
case "$arg" in
--no-pull) DO_PULL=0 ;;
-h|--help)
sed -n '2,22p' "$0"
exit 0
;;
*)
echo "Unknown argument: $arg" >&2
echo "Usage: $0 [--no-pull]" >&2
exit 2
;;
esac
done
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
log() { printf '\n==> %s\n' "$*"; }
die() { printf 'error: %s\n' "$*" >&2; exit 1; }
if [[ "$ROOT" != "$CANONICAL_ROOT" ]]; then
die "Checkout is $ROOT; live deploy must be $CANONICAL_ROOT.
Stop compose in the old tree (often /root/pinscope), move or clone this
repo to $CANONICAL_ROOT (keep .env and data/), then run
$CANONICAL_ROOT/scripts/update-periscope.sh
Override only if the host layout differs: CANONICAL_ROOT=$ROOT $0"
fi
if [[ "${ENVIRONMENT:-}" == "production" ]]; then
die "ENVIRONMENT=production is set. The backend will refuse to start without Clerk. Unset it for this self-hosted instance."
fi
if [[ ! -f docker-compose.yml ]]; then
die "docker-compose.yml not found in $ROOT — run this from the Periscope checkout."
fi
log "checkout $ROOT (compose project: periscope)"
compose() {
if docker compose version >/dev/null 2>&1; then
docker compose "$@"
elif command -v docker-compose >/dev/null 2>&1; then
docker-compose "$@"
else
die "docker compose is not installed"
fi
}
upsert_env() {
local key="$1" value="$2" file="$3"
python3 - "$key" "$value" "$file" <<'PY'
import sys
from pathlib import Path
key, value, path = sys.argv[1], sys.argv[2], Path(sys.argv[3])
text = path.read_text() if path.exists() else ""
lines = text.splitlines()
out = []
found = False
for line in lines:
stripped = line.strip()
if stripped.startswith("#"):
out.append(line)
continue
if stripped.split("=", 1)[0].strip() == key:
out.append(f"{key}={value}")
found = True
else:
out.append(line)
if not found:
if out and out[-1] != "":
out.append("")
out.append(f"{key}={value}")
path.write_text("\n".join(out) + ("\n" if out else ""))
PY
}
read_env() {
local key="$1" file="$2"
python3 - "$key" "$file" <<'PY'
import sys
from pathlib import Path
key, path = sys.argv[1], Path(sys.argv[2])
if not path.exists():
sys.exit(0)
for line in path.read_text().splitlines():
s = line.strip()
if not s or s.startswith("#") or "=" not in s:
continue
k, _, v = s.partition("=")
if k.strip() == key:
print(v)
break
PY
}
if [[ ! -f .env ]]; then
if [[ -f backend/.env ]]; then
log "No ./.env — copying backend/.env"
cp backend/.env .env
elif [[ -f periscope/dependency/backend/.env.example ]]; then
log "No ./.env — copying periscope/dependency/backend/.env.example (you must set DEEPSEEK_API_KEY)"
cp periscope/dependency/backend/.env.example .env
elif [[ -f backend/.env.example ]]; then
log "No ./.env — copying backend/.env.example (you must set DEEPSEEK_API_KEY)"
cp backend/.env.example .env
else
die "No .env found. Create one with DEEPSEEK_API_KEY at $ROOT/.env"
fi
fi
log "Ensuring public URL in .env ($SITE)"
upsert_env NEXT_PUBLIC_API_URL "$SITE" .env
# Allow both Periscope and legacy Pinscope host during transition.
upsert_env CORS_ORIGINS "[\"$SITE\",\"https://pinscope.michelebigi.it\"]" .env
KEY="$(read_env DEEPSEEK_API_KEY .env || true)"
if [[ -z "$KEY" || "$KEY" == "sk-..." ]]; then
die "Set a real DEEPSEEK_API_KEY in $ROOT/.env before updating."
fi
mkdir -p data
if [[ "$DO_PULL" -eq 1 ]]; then
if [[ -d .git ]]; then
log "git pull"
branch="$(git rev-parse --abbrev-ref HEAD)"
git pull --ff-only origin "$branch" || git pull --ff-only
else
log "Not a git checkout — skipping pull (use --no-pull next time to silence this)"
fi
else
log "Skipping git pull (--no-pull)"
fi
# Self-host multi-user auth — after pull so this script's upsert logic is current.
if [[ -z "$(read_env AUTH_JWT_SECRET .env || true)" ]]; then
log "Generating AUTH_JWT_SECRET for local multi-user auth"
upsert_env AUTH_JWT_SECRET "$(openssl rand -hex 32)" .env
fi
upsert_env NEXT_PUBLIC_AUTH_MODE "local" .env
log "docker compose up -d --build (data/ is kept)"
compose up -d --build
# Host Caddy (railway-caddy) is on pinscope_pinscope, not the compose
# project network. Join so reverse_proxy periscope-frontend:3000 resolves.
log "Attach app containers to host Caddy network(s)"
for net in pinscope_pinscope pinscope_periscope; do
docker network inspect "$net" >/dev/null 2>&1 || continue
docker network connect "$net" periscope-frontend 2>/dev/null || true
docker network connect "$net" periscope-backend 2>/dev/null || true
done
log "Waiting for backend"
ok=0
for _ in $(seq 1 30); do
if curl -fsS "http://127.0.0.1:8080/api/library" >/dev/null 2>&1 \
|| curl -fsS "http://127.0.0.1:8080/docs" >/dev/null 2>&1; then
ok=1
break
fi
sleep 1
done
if [[ "$ok" -ne 1 ]]; then
echo "Backend did not become ready on :8080. Last logs:" >&2
compose logs --tail 80 backend >&2 || true
exit 1
fi
log "Waiting for frontend :3000"
ok=0
for _ in $(seq 1 30); do
if curl -fsS -o /dev/null "http://127.0.0.1:3000/" ; then
ok=1
break
fi
sleep 1
done
if [[ "$ok" -ne 1 ]]; then
echo "Frontend did not become ready on :3000. Last logs:" >&2
compose logs --tail 80 frontend >&2 || true
exit 1
fi
log "Done. Site should be $SITE (nginx/Caddy still fronts :3000 / :8080)."
log "App containers are on Docker network pinscope_periscope (aliases: pinscope, periscope-frontend)."
compose ps