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.
217 lines
6.3 KiB
Bash
Executable File
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
|