diff --git a/update-forgejo.sh b/update-forgejo.sh index 7796e8f..c1afc55 100644 --- a/update-forgejo.sh +++ b/update-forgejo.sh @@ -2,12 +2,12 @@ set -euo pipefail ######################################## -# Forgejo Updater -# curl | bash safe +# Seby's Forgejo Instance Updater ######################################## -TTY="/dev/tty" - +######################################## +# Styling +######################################## BOLD="\033[1m" GREEN="\033[32m" YELLOW="\033[33m" @@ -22,240 +22,107 @@ 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 helper (curl | bash safe) +######################################## +TTY="/dev/tty" prompt() { - local msg="$1" out + local msg="$1" + local 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 + local msg="$1" + local ans 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" -} +######################################## +# Config +######################################## +APP_DIR="forgejo" ######################################## -# Args +# Header ######################################## -ASSUME_YES=false -INSTALL_PATH="/usr/local/bin/forgejo" -SERVICE_NAME="forgejo" -VERSION="" +clear +cat << "EOF" -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) + Forgejo Updater | v1.0 -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)) +######################################## +# Checks +######################################## +section "Pre-flight Checks" -VERSION="${1:-}" -if [[ -z "$VERSION" ]]; then - usage +if ! have docker; then + err "Docker is not installed. Aborting." exit 1 fi -# Strip an accidental leading "v" so both "11.0.10" and "v11.0.10" work. -VERSION="${VERSION#v}" +say "Docker detected: $(docker --version)" -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|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}" - -######################################## -# 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." +if ! docker compose version >/dev/null 2>&1; then + err "Docker Compose plugin not found. Aborting." + exit 1 +fi +say "Docker Compose detected: $(docker compose version --short 2>/dev/null || docker compose version)" + +if [[ ! -d "$APP_DIR" ]]; then + err "Directory '${APP_DIR}' not found in $(pwd). Aborting." + exit 1 +fi +say "Found ${APP_DIR}/" + +cd "$APP_DIR" + +if [[ ! -f "docker-compose.yml" && ! -f "compose.yaml" && ! -f "compose.yml" ]]; then + err "No docker-compose file found in ${APP_DIR}/. Aborting." exit 1 fi -say "Downloaded ${BINARY}" ######################################## -# Checksum verification (best-effort; Forgejo publishes .sha256 sidecars) +# Update ######################################## -section "Verifying checksum" +section "Stopping Forgejo & Database" +docker compose down +say "Stopped" -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" +section "Pulling Latest Images" +docker compose pull +say "Images updated" + +section "Starting Forgejo & Database" +docker compose up -d +say "Forgejo is back up" + +######################################## +# Cleanup +######################################## +section "Cleanup" + +if confirm_default_no "Remove old/unused Docker images, containers, and cache?"; then + docker image prune -af + docker container prune -f + docker builder prune -af + say "Old Docker cache cleared" 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 + warn "Skipping cleanup" fi section "Done" -say "Forgejo updated to ${VERSION}" +say "Forgejo update complete."