update forgejo update script

This commit is contained in:
Sebastian Cabrera 2026-07-01 17:40:22 -04:00
parent cf61608b21
commit 75feeb3206
Signed by: okseby
GPG key ID: 2DDBFDEE356CF3DE

View file

@ -1,27 +1,261 @@
#!/bin/bash #!/usr/bin/env bash
set -euo pipefail
if [ -z "$1" ]; then ########################################
echo "Usage: $0 <version>" # Forgejo Updater
echo "Example: $0 11.0.10" # curl | bash safe
########################################
TTY="/dev/tty"
BOLD="\033[1m"
GREEN="\033[32m"
YELLOW="\033[33m"
RED="\033[31m"
BLUE="\033[34m"
RESET="\033[0m"
section() { printf "\n${BLUE}${BOLD}==> %s${RESET}\n" "$*"; }
say() { printf "${GREEN}✔ %s${RESET}\n" "$*"; }
warn() { printf "${YELLOW}⚠ %s${RESET}\n" "$*"; }
err() { printf "${RED}✖ %s${RESET}\n" "$*"; }
have() { command -v "$1" >/dev/null 2>&1; }
need_tty() {
if [[ ! -r "$TTY" || ! -w "$TTY" ]]; then
# No TTY (e.g. non-interactive CI) — that's fine as long as -y was passed.
return 1
fi
return 0
}
prompt() {
local msg="$1" out
printf "${BOLD}%s${RESET}" "$msg" >"$TTY"
IFS= read -r out <"$TTY"
printf "%s" "$out"
}
confirm_default_no() {
local msg="$1" ans
if $ASSUME_YES; then
return 0
fi
if ! need_tty; then
err "No TTY and -y not given — refusing to guess on: $msg"
exit 1 exit 1
fi
ans="$(prompt "$msg [y/N]: ")"
[[ "${ans,,}" == "y" || "${ans,,}" == "yes" ]]
}
require_root_or_sudo() {
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
if have sudo; then
SUDO="sudo"
else
err "Root or sudo required."
exit 1
fi
else
SUDO=""
fi
}
run_with_spinner() {
local label="$1"; shift
("$@") &
local pid=$! spin='|/-\' i=0
printf "\033[?25l" >"$TTY" 2>/dev/null || true
while kill -0 "$pid" 2>/dev/null; do
i=$(( (i + 1) % 4 ))
printf "\r${BLUE}${BOLD}..${RESET} %s %s" "${spin:$i:1}" "$label" >"$TTY" 2>/dev/null || true
sleep 0.12
done
wait "$pid"; local rc=$?
printf "\r\033[K" >"$TTY" 2>/dev/null || true
printf "\033[?25h" >"$TTY" 2>/dev/null || true
return "$rc"
}
########################################
# Args
########################################
ASSUME_YES=false
INSTALL_PATH="/usr/local/bin/forgejo"
SERVICE_NAME="forgejo"
VERSION=""
usage() {
cat <<EOF
Usage: $0 [-y] [-p install_path] [-s service_name] <version>
-y Assume yes to all prompts (non-interactive)
-p PATH Install path (default: /usr/local/bin/forgejo)
-s NAME systemd service name (default: forgejo)
Example: $0 11.0.10
EOF
}
while getopts ":yp:s:h" opt; do
case "$opt" in
y) ASSUME_YES=true ;;
p) INSTALL_PATH="$OPTARG" ;;
s) SERVICE_NAME="$OPTARG" ;;
h) usage; exit 0 ;;
*) usage; exit 1 ;;
esac
done
shift $((OPTIND - 1))
VERSION="${1:-}"
if [[ -z "$VERSION" ]]; then
usage
exit 1
fi fi
# Strip an accidental leading "v" so both "11.0.10" and "v11.0.10" work.
VERSION="${VERSION#v}"
VERSION="$1" need_tty || true
require_root_or_sudo
########################################
# Dependency check
########################################
section "Checking dependencies"
for bin in curl systemctl sha256sum install; do
have "$bin" || { err "Required tool '$bin' not found."; exit 1; }
done
say "All required tools present"
########################################
# Arch detection
########################################
case "$(uname -m)" in case "$(uname -m)" in
x86_64) ARCH="amd64" ;; x86_64|amd64) ARCH="amd64" ;;
aarch64) ARCH="arm64" ;; aarch64|arm64) ARCH="arm64" ;;
armv7l) ARCH="arm-6" ;; armv7l|armv6l) ARCH="arm-6" ;;
*) *)
echo "Unsupported architecture: $(uname -m)" err "Unsupported architecture: $(uname -m)"
exit 1 exit 1
;; ;;
esac esac
BINARY="forgejo-${VERSION}-linux-${ARCH}" BINARY="forgejo-${VERSION}-linux-${ARCH}"
BASE_URL="https://codeberg.org/forgejo/forgejo/releases/download/v${VERSION}"
wget "https://codeberg.org/forgejo/forgejo/releases/download/v${VERSION}/${BINARY}" ########################################
chmod +x "$BINARY" # Sanity checks before touching anything
systemctl stop forgejo ########################################
cp "$BINARY" /usr/local/bin/forgejo if ! systemctl list-unit-files "${SERVICE_NAME}.service" >/dev/null 2>&1; then
systemctl start forgejo.service warn "systemd unit '${SERVICE_NAME}.service' not found — will still attempt but stop/start may fail."
fi
WORKDIR="$(mktemp -d)"
cleanup() { rm -rf "$WORKDIR"; }
trap cleanup EXIT
########################################
# Download
########################################
section "Downloading Forgejo ${VERSION} (${ARCH})"
if ! run_with_spinner "Downloading binary..." \
curl -fL --retry 3 --retry-delay 2 -o "${WORKDIR}/${BINARY}" "${BASE_URL}/${BINARY}"; then
err "Download failed. Check the version number and your network connection."
exit 1
fi
say "Downloaded ${BINARY}"
########################################
# Checksum verification (best-effort; Forgejo publishes .sha256 sidecars)
########################################
section "Verifying checksum"
if curl -fsSL -o "${WORKDIR}/${BINARY}.sha256" "${BASE_URL}/${BINARY}.sha256" 2>/dev/null; then
( cd "$WORKDIR" && sha256sum -c "${BINARY}.sha256" ) || {
err "Checksum verification FAILED. Refusing to install a corrupted/tampered binary."
exit 1
}
say "Checksum verified"
else
warn "No checksum file found upstream — skipping verification."
if ! confirm_default_no "Continue without checksum verification?"; then
err "Aborted by user."
exit 1
fi
fi
chmod +x "${WORKDIR}/${BINARY}"
########################################
# Validate the binary actually runs before going anywhere near the service
########################################
section "Validating binary"
if ! "${WORKDIR}/${BINARY}" --version >/dev/null 2>&1; then
err "Downloaded binary failed to execute (--version check). Aborting before touching the running service."
exit 1
fi
say "Binary runs OK: $("${WORKDIR}/${BINARY}" --version 2>&1 | head -n1)"
########################################
# Backup current binary
########################################
BACKUP=""
if [[ -f "$INSTALL_PATH" ]]; then
BACKUP="${INSTALL_PATH}.bak.$(date +%Y%m%d%H%M%S)"
$SUDO cp -a "$INSTALL_PATH" "$BACKUP"
say "Backed up existing binary to ${BACKUP}"
fi
########################################
# Confirm before disrupting the service
########################################
section "Ready to install"
echo " Version: ${VERSION}"
echo " Path: ${INSTALL_PATH}"
echo " Service: ${SERVICE_NAME}"
if ! confirm_default_no "Stop '${SERVICE_NAME}', replace the binary, and restart?"; then
err "Aborted by user. Nothing was changed."
exit 1
fi
########################################
# Stop, swap (atomically), start
########################################
section "Installing"
$SUDO systemctl stop "${SERVICE_NAME}" || warn "Failed to stop ${SERVICE_NAME} (continuing anyway)"
# `install` does an atomic rename into place rather than an in-place copy,
# so there's never a half-written binary on disk.
$SUDO install -m 0755 "${WORKDIR}/${BINARY}" "$INSTALL_PATH"
say "Installed new binary to ${INSTALL_PATH}"
$SUDO systemctl start "${SERVICE_NAME}" || true
section "Verifying service"
sleep 2
if $SUDO systemctl is-active --quiet "${SERVICE_NAME}"; then
say "${SERVICE_NAME} is active and running"
else
err "${SERVICE_NAME} failed to start after update!"
if [[ -n "$BACKUP" ]]; then
warn "Rolling back to previous binary..."
$SUDO install -m 0755 "$BACKUP" "$INSTALL_PATH"
$SUDO systemctl restart "${SERVICE_NAME}" || true
if $SUDO systemctl is-active --quiet "${SERVICE_NAME}"; then
say "Rollback successful — service restored to previous version"
else
err "Rollback also failed — manual intervention required. Backup at: ${BACKUP}"
fi
else
err "No backup was available to roll back to. Check 'systemctl status ${SERVICE_NAME}' and journalctl."
fi
exit 1
fi
section "Done"
say "Forgejo updated to ${VERSION}"