Files
zwitschi 5d81f617f0 feat(deploy): conflict-free 'git pull' update flow (D001/D004/D005)
- deploy.sh: clean-tree guard + git fetch && git pull --ff-only + optional
  dep reinstall + app.db --migrate + restart.
- restart-service.sh: supervisor-agnostic restart (systemd or fallback), waits
  for an in-flight scan.
- check_runtime_git.sh: proves no scan writes git-tracked runtime data.
- ids.service: systemd unit template.
- tests/test_deployment.py: stdlib unittest invariant tests for the deployment
  runtime-isolation guarantee.
2026-10-24 19:50:45 +02:00

76 lines
2.5 KiB
Bash

#!/usr/bin/env bash
#
# D004 — conflict-free deployment update wrapper.
#
# Updates a deployed instance with `git pull` WITHOUT ever hitting a merge
# conflict from files a scan wrote since the last pull. Guarantees:
#
# 1. Refuses to update over uncommitted tracked changes (clean-tree guard).
# 2. Fast-forwards only (--ff-only): never creates merge commits and never
# silently conflicts.
# 3. Runtime data (data/, reports/, *.db) is gitignored, so a running scan
# leaves no dirty tracked paths and never blocks the pull.
# 4. Reinstalls dependencies only if requirements.txt changed.
# 5. Applies DB migrations idempotently.
# 6. Restarts the app under its supervisor.
#
# Usage:
# sudo deploy/deploy.sh # update + restart (default env: production)
# ENV=production deploy/deploy.sh # use deploy/ids@production.service
# deploy/deploy.sh --no-restart # update deps+migrate but don't restart
set -euo pipefail
cd "$(dirname "$0")/.."
ENV="${ENV:-production}"
RESTART="${1:-restart}"
ROOT="$(pwd)"
PY="${PY:-python}"
echo "== Intrusion Detection deploy (env=$ENV) =="
# 1) Clean-tree guard. Runtime data is gitignored, so this only fails if a
# human edited tracked source without committing.
if ! git diff --quiet HEAD || ! git diff --cached --quiet; then
echo "REFUSING: working tree is not clean." >&2
echo "Commit or stash tracked changes, then re-run." >&2
echo " git status --short" >&2
exit 1
fi
# 2) Pre-flight: prove runtime isolation holds (fail fast before touching repo).
if "$ROOT/deploy/check_runtime_git.sh"; then
echo "== runtime isolation ok =="
else
echo "ABORT: runtime isolation check failed." >&2
exit 1
fi
# 3) Fast-forward only.
echo "== fetching upstream =="
git fetch origin
echo "== fast-forward pull =="
git pull --ff-only
# 4) Dependencies (only if the requirement set changed since the last pull).
if git diff --quiet HEAD@{1} HEAD -- requirements.txt; then
echo "== requirements.txt unchanged; skipping pip =="
else
echo "== requirements.txt changed; installing deps =="
command -v "$PY" >/dev/null && "$PY" -m pip install -r requirements.txt \
|| pip install -r requirements.txt
fi
# 5) Apply DB migrations (idempotent).
echo "== applying DB migrations =="
"$PY" -m app.db --migrate
# 6) Restart under the supervisor.
if [[ "$RESTART" == "--no-restart" ]]; then
echo "== --no-restart: update complete; restart manually =="
else
echo "== restarting service =="
"$ROOT/deploy/restart-service.sh" "$ENV"
fi
echo "== deploy complete =="