Phase A scaffolded the SPA at the bare origin (`/`); production lives behind
Apache at `/social-reports/`. Without these fixes, V2's built assets 404 and
Azure SSO rejects the redirect URI mismatch.
- Vite `base: /social-reports/` (overridable via VITE_BASE for dev).
- BrowserRouter basename = import.meta.env.BASE_URL.
- apiFetch + msal-browser script src + token-exchange URL all prefix BASE.
- MSAL redirectUri now matches V1's Azure-registered URI:
`${origin}/social-reports/login.html`.
- New `<Route path="/login.html">` alias renders the same Login component
so React Router matches the redirect URI when MSAL returns.
Deploy ergonomics (the user wants V1 gone from the server):
- v2/deploy/cutover-in-place.sh: run from /opt/social-reporting; stops V1,
pulls main (v2/ appears, V1 dirs deleted), migrates secrets from V1's
.env into v2/.env, swaps Apache, starts V2. Single command, no clone of
a sibling dir needed.
- setup-v2.sh: PURGE_V1=true flag now cleans /opt/social-reporting and
the V1 docker volume after V2 is healthy.
- rollback-to-v1.sh: re-clones the v1-archive branch when V1 is no longer
on disk (REPO_URL required).
62/62 unit tests still pass; vite build emits assets under /social-reports/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
104 lines
4.7 KiB
Bash
Executable file
104 lines
4.7 KiB
Bash
Executable file
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
# ═══════════════════════════════════════════════════════
|
|
# Social Reporting V2 — Server Setup (one-time)
|
|
# Target: optical-dev.oliver.solutions
|
|
# Replaces V1 at the same URL. V1 is removed from the server; rollback re-clones
|
|
# the v1-archive branch (see rollback-to-v1.sh).
|
|
# ═══════════════════════════════════════════════════════
|
|
|
|
REPO_URL="${REPO_URL:-}"
|
|
BACKEND_DIR_V2="/opt/social-reporting-v2"
|
|
BACKEND_DIR_V1="/opt/social-reporting"
|
|
APACHE_CONF="/etc/apache2/conf-available/social-reports.conf"
|
|
PURGE_V1="${PURGE_V1:-}" # set to 'true' to delete /opt/social-reporting after V2 is healthy
|
|
|
|
GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
|
log() { echo -e "${GREEN}[+]${NC} $1"; }
|
|
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
|
|
err() { echo -e "${RED}[x]${NC} $1"; exit 1; }
|
|
|
|
[[ -z "$REPO_URL" ]] && err "REPO_URL not set"
|
|
command -v docker >/dev/null || err "Docker not installed"
|
|
command -v apache2ctl >/dev/null || err "Apache not installed"
|
|
|
|
# ─── Clone or pull V2 source ───
|
|
if [[ -d "$BACKEND_DIR_V2/.git" ]]; then
|
|
log "Updating V2 repo at $BACKEND_DIR_V2..."
|
|
cd "$BACKEND_DIR_V2" && git remote set-url origin "$REPO_URL" && git pull origin main
|
|
else
|
|
log "Cloning V2 repo to $BACKEND_DIR_V2..."
|
|
sudo mkdir -p "$BACKEND_DIR_V2"
|
|
sudo chown "$(whoami):$(whoami)" "$BACKEND_DIR_V2"
|
|
git clone "$REPO_URL" "$BACKEND_DIR_V2"
|
|
fi
|
|
cd "$BACKEND_DIR_V2"
|
|
|
|
# ─── .env template ───
|
|
if [[ ! -f "$BACKEND_DIR_V2/v2/.env" ]]; then
|
|
warn "v2/.env not found — copying template"
|
|
cp v2/.env.example v2/.env
|
|
SS=$(openssl rand -hex 32)
|
|
sed -i "s/^SESSION_SECRET=$/SESSION_SECRET=${SS}/" v2/.env
|
|
warn "Edit $BACKEND_DIR_V2/v2/.env: APIFY_TOKEN, ANTHROPIC_API_KEY, AZURE_*, BOOTSTRAP_SUPER_ADMIN_EMAIL"
|
|
fi
|
|
|
|
# ─── Cutover ───
|
|
warn "About to take over the /social-reports URL with V2."
|
|
read -r -p "Proceed? [y/N] " ans
|
|
[[ "$ans" != "y" && "$ans" != "Y" ]] && err "Aborted"
|
|
|
|
# Stop V1 stack if it's running (no-op if V1 was never deployed here).
|
|
if [[ -d "$BACKEND_DIR_V1" ]]; then
|
|
log "Stopping V1 stack (if running)..."
|
|
(cd "$BACKEND_DIR_V1" && docker compose -p social-listening down 2>/dev/null) || warn "V1 was not running"
|
|
fi
|
|
|
|
# ─── Apache: swap conf to V2 ───
|
|
log "Backing up old Apache conf and installing V2..."
|
|
[[ -f "$APACHE_CONF" ]] && sudo cp "$APACHE_CONF" "${APACHE_CONF}.v1.bak.$(date +%s)"
|
|
sudo cp "$BACKEND_DIR_V2/v2/deploy/apache-social-reports-v2.conf" "$APACHE_CONF"
|
|
for mod in proxy proxy_http headers rewrite; do
|
|
apache2ctl -M 2>/dev/null | grep -q "${mod}_module" || sudo a2enmod "$mod"
|
|
done
|
|
sudo a2enconf social-reports >/dev/null 2>&1 || true
|
|
sudo apache2ctl configtest || err "Apache config test failed"
|
|
|
|
# ─── Build & start V2 ───
|
|
log "Building & starting V2 stack..."
|
|
cd "$BACKEND_DIR_V2"
|
|
docker compose -f v2/docker-compose.v2.yml -f v2/docker-compose.v2.prod.yml up -d --build
|
|
|
|
log "Waiting for V2 backend (port 3457)..."
|
|
for i in {1..30}; do
|
|
curl -sf http://127.0.0.1:3457/api/health >/dev/null 2>&1 && { log "V2 healthy"; break; }
|
|
[ "$i" -eq 30 ] && err "V2 not responding — check: docker compose -p social-reporting-v2 logs app-v2"
|
|
sleep 2
|
|
done
|
|
|
|
log "Reloading Apache..."
|
|
sudo systemctl reload apache2
|
|
|
|
# ─── Optional V1 purge ───
|
|
if [[ "$PURGE_V1" == "true" && -d "$BACKEND_DIR_V1" ]]; then
|
|
warn "PURGE_V1=true — removing $BACKEND_DIR_V1 and the V1 docker volume"
|
|
docker volume rm social-listening_pgdata 2>/dev/null || warn "(V1 db volume already gone)"
|
|
sudo rm -rf "$BACKEND_DIR_V1"
|
|
log "V1 source and db volume removed. Rollback now re-clones the v1-archive branch."
|
|
fi
|
|
|
|
echo ""
|
|
echo "════════════════════════════════════════════════════"
|
|
echo -e " ${GREEN}V2 deployed!${NC}"
|
|
echo " URL: https://optical-dev.oliver.solutions/social-reports/"
|
|
echo " Backend: http://127.0.0.1:3457 (Docker)"
|
|
echo " V2 dir: $BACKEND_DIR_V2"
|
|
if [[ "$PURGE_V1" == "true" ]]; then
|
|
echo " V1: purged"
|
|
echo " Rollback: git checkout v1-archive on a new clone, then run V1's setup.sh"
|
|
else
|
|
echo " V1 dir: $BACKEND_DIR_V1 (still on disk; remove with PURGE_V1=true on next run)"
|
|
echo " Rollback: bash $BACKEND_DIR_V2/v2/deploy/rollback-to-v1.sh"
|
|
fi
|
|
echo "════════════════════════════════════════════════════"
|