#!/usr/bin/env bash # # ARGUS Enterprise Web Security Platform — updater. # # cd /opt/argus && ./update.sh # (or, if you don't have it locally yet) # curl -fsSL https://git-cloud.weboria.eu/weboria/argus-installer/raw/branch/main/update.sh | bash # # ADR-0051 (argus-appliance repo): this pulls a specific, pinned version -- # never :latest. :latest is three independently-mutable registry tags with # no cross-image transaction; a version resolved from latest-version.json # is a single atomically-committed pointer that the release pipeline only # ever writes AFTER every image it names has finished promoting. Reading # :latest directly (the old behavior here) could observe a torn set mid- # promotion; reading the manifest first cannot, because by the time it # names a version, that version's images are already guaranteed complete. set -euo pipefail INSTALL_DIR="${ARGUS_INSTALL_DIR:-/opt/argus}" MANIFEST_URL="https://git-cloud.weboria.eu/weboria/argus-installer/raw/branch/main/latest-version.json" bold() { printf '\033[1m%s\033[0m\n' "$1"; } info() { printf ' %s\n' "$1"; } ok() { printf ' \033[32m✓\033[0m %s\n' "$1"; } fail() { printf ' \033[31m✗\033[0m %s\n' "$1" >&2; exit 1; } bold "ARGUS Updater" echo [ -f "$INSTALL_DIR/docker-compose.yml" ] || fail "No ARGUS installation found at $INSTALL_DIR (set ARGUS_INSTALL_DIR if you installed elsewhere)." cd "$INSTALL_DIR" API_HOST_PORT="$(grep -oP '^API_HOST_PORT=\K.*' .env 2>/dev/null || true)" API_HOST_PORT="${API_HOST_PORT:-9080}" BEFORE_VERSION="$(curl -fsS "http://127.0.0.1:${API_HOST_PORT}/health" 2>/dev/null | grep -oP '"version"\s*:\s*"\K[^"]+' || echo "unknown")" info "Current version: ${BEFORE_VERSION}" bold "Resolving target version..." MANIFEST="$(curl -fsS "$MANIFEST_URL" 2>/dev/null)" \ || fail "Could not reach the update manifest ($MANIFEST_URL). Nothing was changed -- your installation is untouched. Check your network connection and try again." TARGET_VERSION="$(printf '%s' "$MANIFEST" | grep -oP '"version"\s*:\s*"\K[^"]+' || true)" [ -n "$TARGET_VERSION" ] || fail "The update manifest was malformed (no 'version' field). Nothing was changed -- your installation is untouched." info "Target version: ${TARGET_VERSION}" if [ "$BEFORE_VERSION" = "$TARGET_VERSION" ]; then ok "Already up to date (${TARGET_VERSION})." exit 0 fi # Pinned, not :latest -- see the header comment. Exported only for this # script's own `docker compose` invocations below, not written to .env, so # a later manual `docker compose pull` by the operator still defaults to # :latest exactly as it always has. export ARGUS_VERSION="$TARGET_VERSION" bold "Pulling ${TARGET_VERSION} (pinned, not :latest)..." docker compose pull bold "Restarting..." docker compose up -d echo bold "Waiting for ARGUS to become healthy..." READY=false for _ in $(seq 1 60); do if curl -fsS "http://127.0.0.1:${API_HOST_PORT}/health" >/dev/null 2>&1; then READY=true break fi sleep 5 done if [ "$READY" != "true" ]; then fail "ARGUS did not come back healthy within 5 minutes. Check: docker compose -f $INSTALL_DIR/docker-compose.yml logs" fi AFTER_VERSION="$(curl -fsS "http://127.0.0.1:${API_HOST_PORT}/health" 2>/dev/null | grep -oP '"version"\s*:\s*"\K[^"]+' || echo "unknown")" # The API being healthy only proves the API container itself came up on # some version -- it says nothing about whether the UI and proxy images # actually match. A torn pull (network hiccup mid-pull, disk full on one # image, etc.) would otherwise be reported as a clean success. Check every # service's actual running image tag against the target explicitly. bold "Verifying every service is running ${TARGET_VERSION}..." INCONSISTENT=false for svc in api ui nginx; do CONTAINER="$(docker compose ps -q "$svc" 2>/dev/null || true)" if [ -z "$CONTAINER" ]; then info " ✗ ${svc}: container not found" INCONSISTENT=true continue fi RUNNING_IMAGE="$(docker inspect --format '{{.Config.Image}}' "$CONTAINER" 2>/dev/null || echo "")" RUNNING_TAG="${RUNNING_IMAGE##*:}" if [ "$RUNNING_TAG" = "$TARGET_VERSION" ]; then info " ✓ ${svc}: ${RUNNING_TAG}" else info " ✗ ${svc}: ${RUNNING_TAG:-unknown} (expected ${TARGET_VERSION})" INCONSISTENT=true fi done if [ "$INCONSISTENT" = "true" ]; then fail "Update did not complete cleanly -- not every service is running ${TARGET_VERSION}. Your services were NOT automatically rolled back; investigate (docker compose ps, docker compose logs) before retrying." fi if [ "$AFTER_VERSION" != "$TARGET_VERSION" ]; then fail "Every service's image is ${TARGET_VERSION}, but the API's own /health still reports ${AFTER_VERSION} -- it may not have finished restarting. Check: docker compose -f $INSTALL_DIR/docker-compose.yml logs api" fi ok "ARGUS is healthy and every service is confirmed running ${AFTER_VERSION}" info "Updated: ${BEFORE_VERSION} → ${AFTER_VERSION}"