From 75feeb3206ed75ec3a50adeceed3912510b75ec0 Mon Sep 17 00:00:00 2001 From: Sebastian Cabrera Date: Wed, 1 Jul 2026 17:40:22 -0400 Subject: [PATCH] update forgejo update script --- update-forgejo.sh | 268 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 251 insertions(+), 17 deletions(-) diff --git a/update-forgejo.sh b/update-forgejo.sh index b392ca9..7796e8f 100644 --- a/update-forgejo.sh +++ b/update-forgejo.sh @@ -1,27 +1,261 @@ -#!/bin/bash +#!/usr/bin/env bash +set -euo pipefail -if [ -z "$1" ]; then - echo "Usage: $0 " - echo "Example: $0 11.0.10" +######################################## +# Forgejo Updater +# 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 + 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 < + + -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 +# 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 - x86_64) ARCH="amd64" ;; - aarch64) ARCH="arm64" ;; - armv7l) ARCH="arm-6" ;; - *) - echo "Unsupported architecture: $(uname -m)" - exit 1 - ;; + x86_64|amd64) ARCH="amd64" ;; + aarch64|arm64) ARCH="arm64" ;; + armv7l|armv6l) ARCH="arm-6" ;; + *) + err "Unsupported architecture: $(uname -m)" + exit 1 + ;; esac 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" -systemctl stop forgejo -cp "$BINARY" /usr/local/bin/forgejo -systemctl start forgejo.service +######################################## +# Sanity checks before touching anything +######################################## +if ! systemctl list-unit-files "${SERVICE_NAME}.service" >/dev/null 2>&1; then + 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}"