Ctrl K
ComunidadGuías…

update.sh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bash
# ============================================================================
# vFusion update
# ============================================================================
# Brings this install up to the latest release: fetches the code, backs up
# the database, gets the new containers, and restarts the stack in the same
# shape it was already running.
#
# It exists because the manual sequence has four ways to go wrong, and three
# of them are silent:
#
#   * `docker compose up -d` without the profiles that were running quietly
#     STOPS the tunnel, the MQTT broker or the virtual camera. The stack
#     comes back looking healthy and half of it is gone.
#   * `git pull` fails on package-lock.json, every time, because the
#     frontend container rewrites it on the host at every boot.
#   * Migrations are one-way, and the moment to think about a backup is
#     before them rather than after.
#   * Nobody checks whether the thing that came back up is the new version.
#
# Usage:
#   ./update.sh                 # ask before doing anything irreversible
#   ./update.sh --yes           # don't ask
#   ./update.sh --no-backup     # skip the database dump
#   ./update.sh --check         # say what would happen, change nothing
# ============================================================================

set -euo pipefail

cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

ASSUME_YES=0
DO_BACKUP=1
CHECK_ONLY=0

for arg in "$@"; do
    case "$arg" in
        -y|--yes)     ASSUME_YES=1 ;;
        --no-backup)  DO_BACKUP=0 ;;
        --check|-n)   CHECK_ONLY=1 ;;
        -h|--help)    sed -n '2,26p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
        *) echo "unknown option: $arg (try --help)" >&2; exit 2 ;;
    esac
done

say()  { printf '\033[1;36m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m !\033[0m %s\n' "$*" >&2; }
die()  { printf '\033[1;31m !!\033[0m %s\n' "$*" >&2; exit 1; }

ask() {
    [[ $ASSUME_YES -eq 1 ]] && return 0
    local reply
    read -r -p "$1 [y/N] " reply </dev/tty || return 1
    [[ "$reply" =~ ^[Yy]$ ]]
}

command -v docker >/dev/null 2>&1 || die "docker is not in PATH."
command -v git >/dev/null 2>&1    || die "git is not in PATH."
docker compose version >/dev/null 2>&1 || die "this needs Docker Compose v2 (\`docker compose\`, not \`docker-compose\`)."

# ---------------------------------------------------------------------------
# Which stack is this — built from source, or running published images?
#
# Asking Docker rather than guessing. The image a running container was
# created from is the only answer that cannot be out of date.
# ---------------------------------------------------------------------------
COMPOSE_FILE_ARGS=()
MODE="source"
running_image="$(docker inspect --format '{{.Config.Image}}' vfusion-backend-1 2>/dev/null || true)"
if [[ "$running_image" == ghcr.io/* ]]; then
    MODE="images"
elif [[ -z "$running_image" && -f docker-compose.release.yml && ! -d backend/app ]]; then
    MODE="images"
fi
if [[ "$MODE" == "images" ]]; then
    [[ -f docker-compose.release.yml ]] || die "this install runs published images but docker-compose.release.yml is missing."
    COMPOSE_FILE_ARGS=(-f docker-compose.release.yml)
fi

dc() { docker compose "${COMPOSE_FILE_ARGS[@]}" "$@"; }

# ---------------------------------------------------------------------------
# Which profiles are up.
#
# Compose has no "the profiles I started with" memory, so a plain `up -d`
# treats every profiled service as one you did not ask for and stops it.
# Recover the set from what is actually running.
# ---------------------------------------------------------------------------
declare -A SERVICE_PROFILE=(
    [cloudflared]=cloudflared
    [caddy-quick]=quick
    [cloudflared-quick]=quick
    [mqtt-broker]=mqtt
    [mqtt-tls]=mqtt
    [rtsp-server]=rtsp
)
PROFILE_ARGS=()
ACTIVE_PROFILES=()
running_services="$(dc ps --services --status running 2>/dev/null || true)"
for svc in "${!SERVICE_PROFILE[@]}"; do
    if grep -qx "$svc" <<< "$running_services"; then
        p="${SERVICE_PROFILE[$svc]}"
        if [[ ! " ${ACTIVE_PROFILES[*]-} " == *" $p "* ]]; then
            ACTIVE_PROFILES+=("$p")
            PROFILE_ARGS+=(--profile "$p")
        fi
    fi
done

BRANCH="$(git rev-parse --abbrev-ref HEAD)"
say "vFusion update"
echo "    directory  $(pwd)"
echo "    branch     ${BRANCH}"
echo "    mode       ${MODE} ($([[ $MODE == images ]] && echo 'pull published images' || echo 'build from source'))"
echo "    profiles   ${ACTIVE_PROFILES[*]-none}"

# ---------------------------------------------------------------------------
# What is new.
# ---------------------------------------------------------------------------
say "Fetching ${BRANCH} from origin"
git fetch --quiet --tags origin

behind="$(git rev-list --count "HEAD..origin/${BRANCH}" 2>/dev/null || echo 0)"
if [[ "$behind" -eq 0 ]]; then
    say "Already up to date with origin/${BRANCH}."
    [[ $CHECK_ONLY -eq 1 ]] && exit 0
    ask "Re-deploy the current code anyway?" || exit 0
else
    echo
    git --no-pager log --oneline --no-decorate "HEAD..origin/${BRANCH}" | head -40
    [[ "$behind" -gt 40 ]] && echo "    … and $((behind - 40)) more"
    echo
fi

if [[ $CHECK_ONLY -eq 1 ]]; then
    say "--check: nothing was changed."
    exit 0
fi

# ---------------------------------------------------------------------------
# The package-lock.json problem.
#
# The dev frontend container deletes and regenerates package-lock.json on
# every boot, and the bind mount writes it back to the host. So the working
# tree is nearly always dirty in exactly one file, and `git pull` refuses.
# Throwing away the container's copy is always right: the committed one is
# the source of truth and the container rewrites it again in thirty seconds.
# ---------------------------------------------------------------------------
if ! git diff --quiet -- frontend/package-lock.json 2>/dev/null; then
    say "Discarding the container's rewritten frontend/package-lock.json"
    git checkout -- frontend/package-lock.json
fi

if ! git diff-index --quiet HEAD --; then
    warn "You have other local changes:"
    git --no-pager status --short
    ask "Continue? The pull will stop rather than overwrite them." || exit 1
fi

# ---------------------------------------------------------------------------
# Back up before migrating. Migrations are one-way.
# ---------------------------------------------------------------------------
if [[ $DO_BACKUP -eq 1 ]]; then
    if dc ps --services --status running 2>/dev/null | grep -qx postgres; then
        mkdir -p backups
        stamp="$(date +%Y%m%d-%H%M%S)"
        dest="backups/vfusion-${stamp}.sql.gz"
        say "Backing up the database to ${dest}"
        # -T because there is no terminal here, and pg_dump's output is the
        # payload rather than something to look at.
        if dc exec -T postgres pg_dump -U verkada -d vfusion | gzip > "$dest"; then
            echo "    $(du -h "$dest" | cut -f1)"
        else
            rm -f "$dest"
            warn "The backup failed."
            ask "Continue without one? Migrations cannot be undone." || exit 1
        fi
    else
        warn "Postgres is not running, so there is nothing to back up yet."
    fi
else
    warn "Skipping the backup (--no-backup)."
fi

# ---------------------------------------------------------------------------
# Update.
# ---------------------------------------------------------------------------
if [[ "$behind" -gt 0 ]]; then
    say "Updating the working tree"
    git pull --ff-only --quiet origin "${BRANCH}"
fi

if [[ "$MODE" == "images" ]]; then
    say "Pulling published images"
    dc "${PROFILE_ARGS[@]}" pull
    say "Restarting"
    dc "${PROFILE_ARGS[@]}" up -d --remove-orphans
else
    say "Rebuilding (this is the slow path — see docs/deploying.md for the image-based one)"
    dc "${PROFILE_ARGS[@]}" up -d --build --remove-orphans
fi

# ---------------------------------------------------------------------------
# Did it come back, and is it the new one?
# ---------------------------------------------------------------------------
say "Waiting for the backend"
version=""
for _ in $(seq 1 60); do
    if body="$(curl -fsS http://localhost:18080/api/config 2>/dev/null)"; then
        version="$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' <<< "$body")"
        [[ -n "$version" ]] && break
    fi
    sleep 2
done

echo
if [[ -n "$version" ]]; then
    say "vFusion ${version} is up."
else
    warn "The backend did not answer within two minutes."
    echo "    docker compose ${COMPOSE_FILE_ARGS[*]-} logs -f backend"
    exit 1
fi

# Dangling images from the previous version are not deleted automatically
# and are the usual reason a host runs out of disk three releases later.
say "Old images left behind: docker image prune -f"