- scripts/setup_server.sh: idempotent one-shot. Creates bare repo, post-receive hook (which rebuilds docker compose + gates on /healthz), infoxtractor Postgres role + DB on the shared postgis container, .env (0600) from .env.example with the password substituted in, verifies gpt-oss:20b is pulled. - docs/deployment.md: topology, one-time setup command, normal deploy workflow, rollback-via-revert pattern (never force-push main), operational checklists for the common /healthz degraded states. - First deploy section reserved; filled in after Task 5.3 runs. Task 5.2 of MVP plan. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
127 lines
4.1 KiB
Bash
Executable file
127 lines
4.1 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# One-shot server setup for InfoXtractor. Idempotent: safe to re-run.
|
|
#
|
|
# Run from the Mac:
|
|
# IX_POSTGRES_PASSWORD=<pw> ./scripts/setup_server.sh
|
|
#
|
|
# What it does on 192.168.68.42:
|
|
# 1. Creates the bare git repo `/home/server/Public/infoxtractor/repos.git` if missing.
|
|
# 2. Writes the post-receive hook (or updates it) and makes it executable.
|
|
# 3. Creates the Postgres role + database on the shared `postgis` container.
|
|
# 4. Writes `/home/server/Public/infoxtractor/app/.env` (0600) from .env.example.
|
|
# 5. Verifies `gpt-oss:20b` is pulled in Ollama.
|
|
|
|
set -euo pipefail
|
|
|
|
SERVER="${IX_SERVER:-server@192.168.68.42}"
|
|
APP_BASE="/home/server/Public/infoxtractor"
|
|
REPOS_GIT="${APP_BASE}/repos.git"
|
|
APP_DIR="${APP_BASE}/app"
|
|
DB_NAME="infoxtractor"
|
|
DB_USER="infoxtractor"
|
|
|
|
if [ -z "${IX_POSTGRES_PASSWORD:-}" ]; then
|
|
read -r -s -p "Postgres password for role '${DB_USER}': " IX_POSTGRES_PASSWORD
|
|
echo
|
|
fi
|
|
|
|
if [ -z "${IX_POSTGRES_PASSWORD}" ]; then
|
|
echo "IX_POSTGRES_PASSWORD is required." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "==> 1/5 Ensuring bare repo + post-receive hook on ${SERVER}"
|
|
ssh "${SERVER}" bash -s <<EOF
|
|
set -euo pipefail
|
|
mkdir -p "${REPOS_GIT}" "${APP_DIR}"
|
|
if [ ! -f "${REPOS_GIT}/HEAD" ]; then
|
|
git init --bare "${REPOS_GIT}"
|
|
fi
|
|
|
|
cat >"${REPOS_GIT}/hooks/post-receive" <<'HOOK'
|
|
#!/usr/bin/env bash
|
|
set -eo pipefail
|
|
|
|
APP_DIR="${APP_DIR}"
|
|
LOG="/tmp/infoxtractor-deploy.log"
|
|
|
|
echo "[\$(date -u '+%FT%TZ')] post-receive start" >> "\$LOG"
|
|
|
|
mkdir -p "\$APP_DIR"
|
|
GIT_WORK_TREE="\$APP_DIR" git --git-dir="${REPOS_GIT}" checkout -f main >> "\$LOG" 2>&1
|
|
|
|
cd "\$APP_DIR"
|
|
docker compose up -d --build >> "\$LOG" 2>&1
|
|
|
|
# Deploy gate: /healthz must return 200 within 60 s.
|
|
for i in \$(seq 1 30); do
|
|
if curl -fsS http://localhost:8994/healthz > /dev/null 2>&1; then
|
|
echo "[\$(date -u '+%FT%TZ')] healthz OK" >> "\$LOG"
|
|
exit 0
|
|
fi
|
|
sleep 2
|
|
done
|
|
|
|
echo "[\$(date -u '+%FT%TZ')] healthz never reached OK" >> "\$LOG"
|
|
docker compose logs --tail 100 >> "\$LOG" 2>&1 || true
|
|
exit 1
|
|
HOOK
|
|
|
|
chmod +x "${REPOS_GIT}/hooks/post-receive"
|
|
EOF
|
|
|
|
echo "==> 2/5 Verifying Ollama has gpt-oss:20b pulled"
|
|
if ! ssh "${SERVER}" "docker exec ollama ollama list | awk '{print \$1}' | grep -qx 'gpt-oss:20b'"; then
|
|
echo "FAIL: gpt-oss:20b not found in Ollama. Run: ssh ${SERVER} 'docker exec ollama ollama pull gpt-oss:20b'" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "==> 3/5 Creating Postgres role '${DB_USER}' and database '${DB_NAME}' on postgis container"
|
|
# Idempotent via DO blocks; uses docker exec to avoid needing psql on the host.
|
|
ssh "${SERVER}" bash -s <<EOF
|
|
set -euo pipefail
|
|
docker exec -i postgis psql -U postgres <<SQL
|
|
DO \\\$\\\$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${DB_USER}') THEN
|
|
CREATE ROLE ${DB_USER} LOGIN PASSWORD '${IX_POSTGRES_PASSWORD}';
|
|
ELSE
|
|
ALTER ROLE ${DB_USER} WITH PASSWORD '${IX_POSTGRES_PASSWORD}';
|
|
END IF;
|
|
END
|
|
\\\$\\\$;
|
|
SQL
|
|
|
|
if ! docker exec -i postgis psql -U postgres -tc "SELECT 1 FROM pg_database WHERE datname = '${DB_NAME}'" | grep -q 1; then
|
|
docker exec -i postgis createdb -U postgres -O ${DB_USER} ${DB_NAME}
|
|
fi
|
|
EOF
|
|
|
|
echo "==> 4/5 Writing ${APP_DIR}/.env on the server"
|
|
# Render .env from the repo's .env.example, substituting the password placeholder.
|
|
LOCAL_ENV_CONTENT="$(
|
|
sed "s#<password>#${IX_POSTGRES_PASSWORD}#g" \
|
|
"$(dirname "$0")/../.env.example"
|
|
)"
|
|
# Append the IX_TEST_MODE=production for safety (fake mode stays off).
|
|
# .env is written atomically and permissioned 0600.
|
|
ssh "${SERVER}" "install -d -m 0755 '${APP_DIR}' && cat > '${APP_DIR}/.env' <<'ENVEOF'
|
|
${LOCAL_ENV_CONTENT}
|
|
ENVEOF
|
|
chmod 0600 '${APP_DIR}/.env'"
|
|
|
|
echo "==> 5/5 Checking UFW rule for port 8994 (LAN only)"
|
|
ssh "${SERVER}" "sudo ufw status numbered | grep -F 8994" >/dev/null 2>&1 || {
|
|
echo "NOTE: UFW doesn't yet allow 8994. Run on the server:"
|
|
echo " sudo ufw allow from 192.168.68.0/24 to any port 8994 proto tcp"
|
|
}
|
|
|
|
echo
|
|
echo "Done."
|
|
echo
|
|
echo "Next steps (on the Mac):"
|
|
echo " git remote add server ssh://server@192.168.68.42${REPOS_GIT}"
|
|
echo " git push server main"
|
|
echo " ssh ${SERVER} 'tail -f /tmp/infoxtractor-deploy.log'"
|
|
echo " curl http://192.168.68.42:8994/healthz"
|
|
echo " python scripts/e2e_smoke.py"
|