mirror of
https://github.com/mempool/mempool.git
synced 2026-08-13 12:33:11 +02:00
Merge pull request #6536 from mempool/knorrium/umbrel_updater
Add mempool Umbrel update script
This commit is contained in:
commit
f3ef789d32
1 changed files with 260 additions and 0 deletions
260
scripts/update-umbrel-mempool.sh
Executable file
260
scripts/update-umbrel-mempool.sh
Executable file
|
|
@ -0,0 +1,260 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# update-umbrel-mempool.sh
|
||||
#
|
||||
# Interactively update the Mempool Umbrel app's mempool/frontend and
|
||||
# mempool/backend Docker images.
|
||||
#
|
||||
# What it does:
|
||||
# 1. Picks (or prompts for) the SSH target for your Umbrel.
|
||||
# 2. Reads the current versions from the app's docker-compose.yml (over SSH).
|
||||
# 3. Shows the last 10 tags for each image from Docker Hub and lets you choose.
|
||||
# 4. Shows a before/after diff (warns if frontend/backend versions differ).
|
||||
# 5. After you confirm, edits the compose file in place (with a .bak backup) and
|
||||
# runs `umbreld client apps.update.mutate --appId mempool` to apply.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/update-umbrel-mempool.sh [ssh-target]
|
||||
#
|
||||
# With no argument it prompts for the target (default umbrel@umbrel.local).
|
||||
# e.g. ./scripts/update-umbrel-mempool.sh pi@192.168.1.50
|
||||
#
|
||||
# Re-exec under bash if started another way (e.g. `sh script`), and leave POSIX
|
||||
# mode so arrays and other bash features work regardless of how it was launched.
|
||||
if [ -z "${BASH_VERSION:-}" ]; then
|
||||
exec bash "$0" "$@"
|
||||
fi
|
||||
set +o posix 2>/dev/null || true
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
DEFAULT_TARGET="umbrel@umbrel.local"
|
||||
FRONTEND_REPO="mempool/frontend"
|
||||
BACKEND_REPO="mempool/backend"
|
||||
TAG_LIST_COUNT=10
|
||||
|
||||
# Candidate locations for the app's docker-compose.yml on the device. The first
|
||||
# one that exists is used. The bare relative path matches the umbrel user's home.
|
||||
COMPOSE_REL="umbrel/app-stores/getumbrel-umbrel-apps-github-53f74447/mempool/docker-compose.yml"
|
||||
COMPOSE_CANDIDATES=(
|
||||
"\$HOME/$COMPOSE_REL"
|
||||
"/home/umbrel/$COMPOSE_REL"
|
||||
"$COMPOSE_REL"
|
||||
)
|
||||
|
||||
err() { printf 'error: %s\n' "$*" >&2; }
|
||||
die() { err "$*"; exit 1; }
|
||||
info() { printf '%s\n' "$*"; }
|
||||
|
||||
# --- Step 1: preflight ------------------------------------------------------
|
||||
command -v jq >/dev/null 2>&1 || die "jq is required (install: brew install jq)"
|
||||
command -v ssh >/dev/null 2>&1 || die "ssh is required"
|
||||
command -v curl >/dev/null 2>&1 || die "curl is required"
|
||||
[ -e /dev/tty ] || die "this script is interactive and needs a terminal (/dev/tty)"
|
||||
|
||||
# --- Step 2: choose SSH target ----------------------------------------------
|
||||
if [ "$#" -ge 1 ] && [ -n "$1" ]; then
|
||||
SSH_TARGET="$1"
|
||||
info "Using SSH target: ${SSH_TARGET}"
|
||||
else
|
||||
printf 'SSH target [%s]: ' "$DEFAULT_TARGET" >/dev/tty
|
||||
read -r SSH_TARGET </dev/tty || true
|
||||
[ -n "$SSH_TARGET" ] || SSH_TARGET="$DEFAULT_TARGET"
|
||||
fi
|
||||
|
||||
# --- Step 3: locate compose file + read current values (over SSH) -----------
|
||||
# Output contract from the remote read:
|
||||
# line 1: COMPOSE_PATH=<resolved absolute path>
|
||||
# then: the matching `image: mempool/...` lines
|
||||
REMOTE_READ=$(cat <<REMOTE
|
||||
set -e
|
||||
compose=""
|
||||
for p in ${COMPOSE_CANDIDATES[*]}; do
|
||||
if [ -f "\$p" ]; then compose="\$p"; break; fi
|
||||
done
|
||||
if [ -z "\$compose" ]; then echo "COMPOSE_NOT_FOUND" >&2; exit 3; fi
|
||||
echo "COMPOSE_PATH=\$compose"
|
||||
grep -E 'image:[[:space:]]*mempool/(frontend|backend):' "\$compose"
|
||||
REMOTE
|
||||
)
|
||||
|
||||
info "Connecting to ${SSH_TARGET} to read current versions..."
|
||||
REMOTE_OUT="$(ssh "$SSH_TARGET" "$REMOTE_READ")" || {
|
||||
err "Could not read the compose file on ${SSH_TARGET}."
|
||||
err "Checked: ${COMPOSE_CANDIDATES[*]}"
|
||||
die "Verify the SSH target and that the Mempool app is installed."
|
||||
}
|
||||
|
||||
COMPOSE_PATH="$(printf '%s\n' "$REMOTE_OUT" | sed -n 's/^COMPOSE_PATH=//p')"
|
||||
[ -n "$COMPOSE_PATH" ] || die "could not resolve compose path on device"
|
||||
|
||||
# Extract current "repo:tag@sha256:digest" tokens and the bare current tag.
|
||||
cur_ref() { # $1 = repo
|
||||
printf '%s\n' "$REMOTE_OUT" | grep -oE "$1:[^[:space:]\"']+" | head -1
|
||||
}
|
||||
cur_tag() { # $1 = full ref (repo:tag@sha256:...)
|
||||
printf '%s' "$1" | sed -E 's#^[^:]+:([^@]+)@.*#\1#'
|
||||
}
|
||||
FE_CUR="$(cur_ref "$FRONTEND_REPO" || true)"
|
||||
BE_CUR="$(cur_ref "$BACKEND_REPO" || true)"
|
||||
FE_CUR_TAG="$(cur_tag "$FE_CUR")"
|
||||
BE_CUR_TAG="$(cur_tag "$BE_CUR")"
|
||||
|
||||
info ""
|
||||
info "Currently installed on ${SSH_TARGET}:"
|
||||
info " frontend: ${FE_CUR_TAG:-<not found>}"
|
||||
info " backend: ${BE_CUR_TAG:-<not found>}"
|
||||
|
||||
# --- Step 4: interactive tag selection from Docker Hub ----------------------
|
||||
# Choose a tag filter once; it applies to both images for consistency.
|
||||
echo "" >/dev/tty
|
||||
echo "Tag filter:" >/dev/tty
|
||||
echo " 1) Stable releases only (vX.Y.Z)" >/dev/tty
|
||||
echo " 2) All tags (including -dev / -rc / -beta)" >/dev/tty
|
||||
while :; do
|
||||
printf "Enter number [1-2] (default 1): " >/dev/tty
|
||||
read -r FILTER_SEL </dev/tty || true
|
||||
[ -n "$FILTER_SEL" ] || FILTER_SEL=1
|
||||
case "$FILTER_SEL" in
|
||||
1) FILTER_MODE="stable"; break ;;
|
||||
2) FILTER_MODE="all"; break ;;
|
||||
*) echo " Please enter 1 or 2." >/dev/tty ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# jq selector for the chosen filter. Stable = plain semver vX.Y.Z (no suffix).
|
||||
# Both modes require a non-null top-level digest (needed for the @sha256: pin;
|
||||
# some old tags lack one).
|
||||
case "$FILTER_MODE" in
|
||||
stable) JQ_SELECT='select(.digest != null and (.name | test("^v[0-9]+\\.[0-9]+\\.[0-9]+$")))' ;;
|
||||
all) JQ_SELECT='select(.digest != null and .name != "latest")' ;;
|
||||
esac
|
||||
|
||||
# Prints the chosen "<tag> <sha256:digest>" to stdout; menu/prompts go to /dev/tty
|
||||
# so they don't pollute the captured value. $3 = current tag (annotated as such).
|
||||
# Paginates through Docker Hub (newest first) until TAG_LIST_COUNT matches are
|
||||
# collected or pages run out — needed for stable tags buried behind many dev builds.
|
||||
choose_image() {
|
||||
local repo="$1" label="$2" current="${3:-}"
|
||||
local url="https://hub.docker.com/v2/repositories/${repo}/tags?page_size=100&ordering=last_updated"
|
||||
local tags=() digests=() t d resp parsed page=0
|
||||
|
||||
while [ -n "$url" ] && [ "$page" -lt 10 ] && [ "${#tags[@]}" -lt "$TAG_LIST_COUNT" ]; do
|
||||
page=$((page + 1))
|
||||
resp="$(curl -fsSL "$url")" || { err "Docker Hub query failed for ${repo}"; return 1; }
|
||||
parsed="$(printf '%s' "$resp" \
|
||||
| jq -r ".results | map(${JQ_SELECT}) | .[] | \"\(.name)\t\(.digest)\"")"
|
||||
# heredoc (not process substitution) so the loop runs in this shell and the
|
||||
# array appends persist, and so it works even under bash POSIX mode.
|
||||
while IFS=$'\t' read -r t d; do
|
||||
[ -n "$t" ] || continue
|
||||
tags+=("$t"); digests+=("$d")
|
||||
[ "${#tags[@]}" -ge "$TAG_LIST_COUNT" ] && break
|
||||
done <<EOF
|
||||
$parsed
|
||||
EOF
|
||||
url="$(printf '%s' "$resp" | jq -r '.next // empty')"
|
||||
done
|
||||
|
||||
if [ "${#tags[@]}" -eq 0 ]; then
|
||||
err "no ${FILTER_MODE} tags found for ${repo} in the most recent ~1000 tags"
|
||||
[ "$FILTER_MODE" = "stable" ] && err "try re-running and choosing 'All tags'"
|
||||
return 1
|
||||
fi
|
||||
|
||||
{
|
||||
echo ""
|
||||
echo "Select ${label} version (${repo}) — newest first:"
|
||||
local i mark
|
||||
for i in "${!tags[@]}"; do
|
||||
mark=""
|
||||
[ "${tags[$i]}" = "$current" ] && mark=" <- current"
|
||||
printf " %2d) %s%s\n" "$((i + 1))" "${tags[$i]}" "$mark"
|
||||
done
|
||||
} >/dev/tty
|
||||
|
||||
local sel idx
|
||||
while :; do
|
||||
printf "Enter number [1-%d] (default 1): " "${#tags[@]}" >/dev/tty
|
||||
read -r sel </dev/tty || true
|
||||
[ -n "$sel" ] || sel=1
|
||||
case "$sel" in
|
||||
*[!0-9]*) echo " Please enter a number." >/dev/tty; continue ;;
|
||||
esac
|
||||
if [ "$sel" -ge 1 ] && [ "$sel" -le "${#tags[@]}" ]; then idx=$((sel - 1)); break; fi
|
||||
echo " Out of range." >/dev/tty
|
||||
done
|
||||
|
||||
printf '%s %s\n' "${tags[$idx]}" "${digests[$idx]}"
|
||||
}
|
||||
|
||||
read -r FE_TAG FE_DIGEST <<<"$(choose_image "$FRONTEND_REPO" frontend "$FE_CUR_TAG")"
|
||||
[ -n "${FE_TAG:-}" ] && [ -n "${FE_DIGEST:-}" ] && [ "$FE_DIGEST" != "null" ] \
|
||||
|| die "frontend selection failed"
|
||||
|
||||
read -r BE_TAG BE_DIGEST <<<"$(choose_image "$BACKEND_REPO" backend "$BE_CUR_TAG")"
|
||||
[ -n "${BE_TAG:-}" ] && [ -n "${BE_DIGEST:-}" ] && [ "$BE_DIGEST" != "null" ] \
|
||||
|| die "backend selection failed"
|
||||
|
||||
FE_NEW="${FRONTEND_REPO}:${FE_TAG}@${FE_DIGEST}"
|
||||
BE_NEW="${BACKEND_REPO}:${BE_TAG}@${BE_DIGEST}"
|
||||
|
||||
# --- Step 5: show diff + confirm --------------------------------------------
|
||||
info ""
|
||||
info "Compose file: ${COMPOSE_PATH} (on ${SSH_TARGET})"
|
||||
info ""
|
||||
info "frontend:"
|
||||
info " current: ${FE_CUR:-<not found>}"
|
||||
info " new: ${FE_NEW}"
|
||||
info "backend:"
|
||||
info " current: ${BE_CUR:-<not found>}"
|
||||
info " new: ${BE_NEW}"
|
||||
info ""
|
||||
|
||||
if [ "$FE_TAG" != "$BE_TAG" ]; then
|
||||
info "WARNING: selected frontend (${FE_TAG}) and backend (${BE_TAG}) versions differ."
|
||||
info ""
|
||||
fi
|
||||
|
||||
if [ "$FE_CUR" = "$FE_NEW" ] && [ "$BE_CUR" = "$BE_NEW" ]; then
|
||||
info "Both images already match the selected versions. Nothing to do."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf 'Apply update and restart mempool? [y/N] ' >/dev/tty
|
||||
read -r REPLY </dev/tty || true
|
||||
case "${REPLY:-}" in
|
||||
y|Y) ;;
|
||||
*) info "Aborted. No changes made."; exit 0 ;;
|
||||
esac
|
||||
|
||||
# --- Step 6: edit compose file in place over SSH (with backup) --------------
|
||||
# mempool/frontend: and mempool/backend: are distinct prefixes, so the two
|
||||
# substitutions never collide. Replace the whole whitespace/quote-delimited token.
|
||||
REMOTE_EDIT=$(cat <<REMOTE
|
||||
set -e
|
||||
cp "$COMPOSE_PATH" "$COMPOSE_PATH.bak"
|
||||
sed -i \
|
||||
-e 's#${FRONTEND_REPO}:[^[:space:]"'\'']*#${FE_NEW}#' \
|
||||
-e 's#${BACKEND_REPO}:[^[:space:]"'\'']*#${BE_NEW}#' \
|
||||
"$COMPOSE_PATH"
|
||||
echo "--- updated image lines ---"
|
||||
grep -E 'image:[[:space:]]*mempool/(frontend|backend):' "$COMPOSE_PATH"
|
||||
REMOTE
|
||||
)
|
||||
|
||||
info ""
|
||||
info "Editing compose file on ${SSH_TARGET} (backup: ${COMPOSE_PATH}.bak)..."
|
||||
ssh "$SSH_TARGET" "$REMOTE_EDIT" || die "failed to edit compose file"
|
||||
|
||||
# --- Step 7: trigger the update ---------------------------------------------
|
||||
info ""
|
||||
info "Triggering app update..."
|
||||
ssh "$SSH_TARGET" "umbreld client apps.update.mutate --appId mempool" \
|
||||
|| die "umbreld update command failed (compose backup at ${COMPOSE_PATH}.bak)"
|
||||
|
||||
info ""
|
||||
info "Done. Mempool updated:"
|
||||
info " frontend -> ${FE_TAG}"
|
||||
info " backend -> ${BE_TAG}"
|
||||
info "Backup of previous compose: ${COMPOSE_PATH}.bak"
|
||||
Loading…
Add table
Add a link
Reference in a new issue