File size: 4,263 Bytes
cd8bd0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#!/usr/bin/env bash
# bin/rollback.sh β€” roll OmniRoute back to a previous release to mitigate a bad
# deploy. Used by the incident runbook (docs/INCIDENT_RESPONSE.md Β§3 / Β§4).
#
# Methods (auto-detected; override with --method):
#   β€’ npm    β€” `npm install -g omniroute@<version>` and, if PM2 manages it,
#              `pm2 restart omniroute`. This is how the VPS deploy runs.
#   β€’ docker β€” re-tag the local image omniroute:<version> to omniroute:prod and
#              recreate the prod service from docker-compose.prod.yml. (That
#              compose builds the `prod` tag locally rather than pulling a
#              registry tag, so the versioned image must already exist locally.)
# With no <version>, targets the highest published release strictly below the
# current package.json version.
set -euo pipefail
SCRIPT_NAME="rollback"
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_ops-common.sh"
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"

usage() {
  cat <<'EOF'
Usage: bin/rollback.sh [<version>] [--method npm|docker] [--yes|-y] [-h|--help]

Rolls OmniRoute back to <version> (e.g. 3.8.35 or v3.8.35). With no version,
picks the highest published release below the current package.json version.
Auto-detects npm vs docker deployment; override with --method.
EOF
}

VERSION=""
METHOD=""
while [ $# -gt 0 ]; do
  case "$1" in
    --yes | -y) ASSUME_YES=1; shift ;;
    --method) METHOD="${2:?--method needs npm|docker}"; shift 2 ;;
    -h | --help) usage; exit 0 ;;
    -*) ops_die "unknown argument: $1 (see --help)" ;;
    *) VERSION="${1#v}"; shift ;;
  esac
done

if [ -z "$METHOD" ]; then
  if command -v docker >/dev/null 2>&1 && [ -f "$REPO_ROOT/docker-compose.prod.yml" ] \
    && docker compose -f "$REPO_ROOT/docker-compose.prod.yml" ps -q 2>/dev/null | grep -q .; then
    METHOD="docker"
  elif command -v npm >/dev/null 2>&1; then
    METHOD="npm"
  else
    ops_die "no deploy method detected (no running prod compose, no npm) β€” pass --method npm|docker"
  fi
fi

# Resolve the previous published version when none was given.
if [ -z "$VERSION" ]; then
  ops_require_cmd npm
  ops_require_cmd node
  current="$(node -p "require('$REPO_ROOT/package.json').version" 2>/dev/null || true)"
  [ -n "$current" ] || ops_die "cannot read current version from package.json β€” pass <version>"
  VERSION="$(npm view omniroute versions --json 2>/dev/null | node -e '
    let s = "";
    process.stdin.on("data", (d) => (s += d)).on("end", () => {
      let vs;
      try { vs = JSON.parse(s); } catch { vs = []; }
      if (!Array.isArray(vs)) vs = [vs];
      const ok = (v) => /^[0-9]+\.[0-9]+\.[0-9]+$/.test(v);
      const cmp = (a, b) => {
        const x = a.split(".").map(Number), y = b.split(".").map(Number);
        return x[0] - y[0] || x[1] - y[1] || x[2] - y[2];
      };
      const cur = process.argv[1];
      const prev = vs.filter(ok).filter((v) => cmp(v, cur) < 0).sort(cmp).pop() || "";
      process.stdout.write(prev);
    });
  ' "$current")"
  [ -n "$VERSION" ] || ops_die "could not resolve the previous published version β€” pass <version> explicitly"
fi

ops_log "target: omniroute@$VERSION via $METHOD"
ops_confirm "Roll OmniRoute back to $VERSION via $METHOD?" || ops_die "aborted"

case "$METHOD" in
  npm)
    ops_require_cmd npm
    npm install -g "omniroute@$VERSION"
    if command -v pm2 >/dev/null 2>&1 && pm2 jlist 2>/dev/null | grep -q '"name":"omniroute"'; then
      pm2 restart omniroute --update-env
      ops_log "pm2 restarted omniroute"
    else
      ops_log "installed omniroute@$VERSION β€” restart the service to apply (no PM2 'omniroute' process found)"
    fi
    ;;
  docker)
    ops_require_cmd docker
    if ! docker image inspect "omniroute:$VERSION" >/dev/null 2>&1; then
      ops_die "local image omniroute:$VERSION not found β€” build it from the $VERSION checkout first (this compose builds the 'prod' tag, it does not pull a registry tag)"
    fi
    docker tag "omniroute:$VERSION" omniroute:prod
    docker compose -f "$REPO_ROOT/docker-compose.prod.yml" up -d --no-build
    ops_log "recreated prod service from omniroute:$VERSION"
    ;;
  *) ops_die "unknown method: $METHOD (use npm or docker)" ;;
esac
ops_log "rollback to $VERSION complete"