#!/usr/bin/env bash # # upgrade.sh — bring an existing (bare-metal) MAESTRO checkout safely up to the # latest version after a `git pull`. # # Why this exists # --------------- # `scripts/server.sh restart` rebuilds the server (dist/) but NOT the UI: # `ui/dist` is gitignored and built separately, and npm deps are never # refreshed. So after a `git pull` that touched dependencies or the frontend, a # plain restart can leave a stale/missing UI bundle or ABI-mismatched native # modules. This script runs the full, correct update sequence instead: # # git pull (optional) -> build-all.sh (deps + server + UI) -> restart # # Network bind migration (2026-06-10) # ----------------------------------- # A security fix changed the default bind from 0.0.0.0 to 127.0.0.1 (loopback # only). If you used to reach MAESTRO from another machine on a bare-metal # install, after upgrading you'll get ERR_CONNECTION_REFUSED until you set HOST # explicitly. This script detects that and offers to write HOST to .env (with a # warning that you must enable auth before exposing a non-loopback interface). # # Usage: # scripts/upgrade.sh # interactive: pull, build, restart # scripts/upgrade.sh --no-pull # skip `git pull` (build + restart only) # scripts/upgrade.sh --no-restart # build only, leave the server as-is # scripts/upgrade.sh --yes # non-interactive (CI); never edits .env # scripts/upgrade.sh --help # set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" ENV_FILE="$PROJECT_DIR/.env" DO_PULL=1 DO_RESTART=1 ASSUME_YES=0 for arg in "$@"; do case "$arg" in --no-pull) DO_PULL=0 ;; --no-restart) DO_RESTART=0 ;; --yes|-y) ASSUME_YES=1 ;; -h|--help) sed -n '2,29p' "$0" | sed 's/^# \{0,1\}//' exit 0 ;; *) echo "upgrade.sh: unknown option '$arg' (try --help)" >&2 exit 2 ;; esac done cd "$PROJECT_DIR" say() { printf '\n\033[1;36m==>\033[0m %s\n' "$1"; } warn() { printf '\033[1;33mWARN:\033[0m %s\n' "$1" >&2; } # --------------------------------------------------------------------------- # 1. git pull (fast-forward only; never silently merge or rewrite local work) # --------------------------------------------------------------------------- if [[ "$DO_PULL" -eq 1 ]]; then if [[ -n "$(git -C "$PROJECT_DIR" status --porcelain 2>/dev/null)" ]]; then warn "working tree has local changes — skipping 'git pull'. Commit/stash and re-run, or use --no-pull." else say "Pulling latest changes (git pull --ff-only)" if ! git -C "$PROJECT_DIR" pull --ff-only; then warn "git pull --ff-only failed (diverged or offline). Continuing with the current checkout." fi fi else say "Skipping git pull (--no-pull)" fi # --------------------------------------------------------------------------- # 2. Full rebuild: refresh deps + build server dist/ AND ui/dist/ + prebake. # This is the step a plain `server.sh restart` skips for the UI. # --------------------------------------------------------------------------- say "Rebuilding (deps + server + UI) via scripts/build-all.sh" "$SCRIPT_DIR/build-all.sh" # --------------------------------------------------------------------------- # 3. Network bind migration check (0.0.0.0 -> 127.0.0.1 default since 2026-06-10) # --------------------------------------------------------------------------- host_in_env="" if [[ -f "$ENV_FILE" ]]; then # First non-comment HOST=... line, value with surrounding quotes stripped. host_in_env="$(grep -E '^[[:space:]]*(export[[:space:]]+)?HOST=' "$ENV_FILE" \ | grep -vE '^[[:space:]]*#' \ | head -1 | sed -E 's/^[[:space:]]*(export[[:space:]]+)?HOST=//; s/^["'\'']//; s/["'\'']$//')" fi say "Checking network bind" if [[ -n "${HOST:-}" ]]; then echo " HOST is set in the environment ($HOST) — bind is explicit, nothing to migrate." elif [[ -n "$host_in_env" ]]; then echo " HOST=$host_in_env found in .env — bind is explicit, nothing to migrate." else cat <<'EOF' HOST is not set, so the server now binds 127.0.0.1 (loopback only). Since 2026-06-10 the default changed from 0.0.0.0 for security: the agent API includes a Bash tool, so an auth-less instance on the LAN is effectively unauthenticated remote code execution. If you ONLY open MAESTRO from this same machine (http://localhost:9876), no action is needed. If you reach it from ANOTHER machine, that is why you now get ERR_CONNECTION_REFUSED. To restore remote access you must set HOST explicitly -- and you should enable auth (auth.local or OAuth) in config.yaml before exposing a non-loopback interface. EOF if [[ "$ASSUME_YES" -eq 0 ]]; then printf '\n Append HOST=0.0.0.0 to .env now? Only do this if auth is (or will be) enabled. [y/N] ' read -r reply || reply="" if [[ "$reply" =~ ^[Yy]$ ]]; then { [[ -f "$ENV_FILE" && -n "$(tail -c1 "$ENV_FILE" 2>/dev/null)" ]] && echo "" >> "$ENV_FILE"; } || true printf '\n# Added by scripts/upgrade.sh: restore non-loopback bind (enable auth first!)\nHOST=0.0.0.0\n' >> "$ENV_FILE" echo " Wrote HOST=0.0.0.0 to $ENV_FILE" else echo " Left .env unchanged. Set HOST=0.0.0.0 in .env manually when ready." fi else warn "non-interactive (--yes): not editing .env. Set HOST in .env manually if you need remote access." fi fi # --------------------------------------------------------------------------- # 4. Restart # --------------------------------------------------------------------------- if [[ "$DO_RESTART" -eq 1 ]]; then say "Restarting server (scripts/server.sh restart)" "$SCRIPT_DIR/server.sh" restart echo echo "Done. If the server is healthy, open it at the bind address reported above" echo "(loopback default: http://localhost:9876)." else say "Build complete (--no-restart). Start the server with: scripts/server.sh restart" fi