Skip to content

Instantly share code, notes, and snippets.

@quonic
Created September 7, 2026 13:36
Show Gist options
  • Select an option

  • Save quonic/7996547a47e935faeb82f6466ef122e8 to your computer and use it in GitHub Desktop.

Select an option

Save quonic/7996547a47e935faeb82f6466ef122e8 to your computer and use it in GitHub Desktop.
Forgejo update script for a binary install based on Forgejo's documented install steps.
#!/usr/bin/env bash
#
# update-forgejo.sh
#
# Updates a binary installation of Forgejo to the latest release.
#
# Assumptions:
# - Forgejo is already installed and running (or at least was installed at some point).
# - The binary lives at FORGEJO_BIN (default: /usr/local/bin/forgejo).
# - The config file is at FORGEJO_INI (default: /etc/forgejo/app.ini).
# - Managed by systemd as the "forgejo" service.
# - Running as root (or with sudo) so we can manage the service and replace the binary.
#
# Usage:
# sudo ./update-forgejo.sh
#
# Environment overrides:
# FORGEJO_BIN Path to the forgejo binary (default: /usr/local/bin/forgejo)
# FORGEJO_INI Path to app.ini (default: /etc/forgejo/app.ini)
# FORGEJO_SVC systemd unit name (default: forgejo)
# ARCH Target architecture (default: amd64)
#
set -euo pipefail
# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
FORGEJO_BIN="${FORGEJO_BIN:-/usr/local/bin/forgejo}"
FORGEJO_INI="${FORGEJO_INI:-/etc/forgejo/app.ini}"
FORGEJO_SVC="${FORGEJO_SVC:-forgejo}"
ARCH="${ARCH:-amd64}"
RELEASE_BASE="https://code.forgejo.org/forgejo/forgejo/releases"
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
die() {
echo "ERROR: $*" >&2
exit 1
}
log() {
echo "==> $*"
}
require_root() {
if [[ $(id -u) -ne 0 ]]; then
die "This script must be run as root (try: sudo $0)"
fi
}
require_cmd() {
command -v "$1" &>/dev/null || die "Required command '$1' not found in PATH."
}
# ---------------------------------------------------------------------------
# Pre-flight checks
# ---------------------------------------------------------------------------
require_root
require_cmd curl
require_cmd systemctl
# Ensure the binary exists at the expected path.
if [[ ! -x "$FORGEJO_BIN" ]]; then
die "Forgejo binary not found or not executable at: $FORGEJO_BIN
If you installed it elsewhere, set FORGEJO_BIN and re-run:
FORGEJO_BIN=/path/to/forgejo sudo $0"
fi
# Sanity-check the config file.
if [[ ! -f "$FORGEJO_INI" ]]; then
die "Config file not found at: $FORGEJO_INI
If your config lives elsewhere, set FORGEJO_INI and re-run:
FORGEJO_INI=/path/to/app.ini sudo $0"
fi
log "Current installation:"
log " Binary : $FORGEJO_BIN"
log " Config : $FORGEJO_INI"
log " Service: $FORGEJO_SVC"
CURRENT_VER=$("$FORGEJO_BIN" -c "$FORGEJO_INI" -v 2>/dev/null || echo "unknown")
log " Version: $CURRENT_VER"
# ---------------------------------------------------------------------------
# Determine the latest release tag via the /releases/latest redirect
# ---------------------------------------------------------------------------
log "Resolving latest release tag from $RELEASE_BASE/latest ..."
# -o /dev/null : don't save body
# -w '%{url_effective}' : print the final (post-redirect) URL
# -L : follow redirects
# -s : silent
# -I : HEAD request (we only need the Location header)
LATEST_URL=$(curl -sL -o /dev/null -w '%{url_effective}' -I \
"${RELEASE_BASE}/latest/")
# The effective URL should look like:
# https://code.forgejo.org/forgejo/forgejo/releases/tag/v9.0.1
# Extract the tag (last path segment).
TAG=$(basename "$LATEST_URL")
# Strip a leading 'v' if present → v9.0.1 → 9.0.1
VERSION="${TAG#v}"
if [[ -z "$TAG" || "$TAG" == "$RELEASE_BASE" ]]; then
die "Could not resolve latest release tag. Raw URL: $LATEST_URL"
fi
log "Latest release tag: $TAG (version $VERSION)"
log "Current version : $CURRENT_VER"
# Short-circuit if we're already on the latest.
if [[ "$VERSION" == *"${CURRENT_VER#Forgejo }"* ]]; then
log "Already up to date. Nothing to do."
exit 0
fi
# ---------------------------------------------------------------------------
# Build the download URL
# ---------------------------------------------------------------------------
# Forgejo release assets are named: forgejo-<version>-linux-<arch>
ASSET="forgejo-${VERSION}-linux-${ARCH}"
DOWNLOAD_URL="${RELEASE_BASE}/download/${TAG}/${ASSET}"
log "Downloading: $DOWNLOAD_URL"
TMP_BIN=$(mktemp /tmp/forgejo-update.XXXXXX)
trap 'rm -f "$TMP_BIN"' EXIT
curl -fSL --progress-bar -o "$TMP_BIN" "$DOWNLOAD_URL" \
|| die "Download failed for $DOWNLOAD_URL"
# ---------------------------------------------------------------------------
# Verify the downloaded binary
# ---------------------------------------------------------------------------
chmod +x "$TMP_BIN"
if ! "$TMP_BIN" -c "$FORGEJO_INI" -v &>/dev/null; then
die "The downloaded binary failed its sanity check (could not run -v). Aborting."
fi
log "New binary passes sanity check."
# ---------------------------------------------------------------------------
# Stop the service
# ---------------------------------------------------------------------------
log "Stopping $FORGEJO_SVC service ..."
if systemctl is-active --quiet "$FORGEJO_SVC"; then
systemctl stop "$FORGEJO_SVC"
else
log " Service is not currently running; skipping stop."
fi
log "Installing new binary to $FORGEJO_BIN"
install -m 0755 -o root -g root "$TMP_BIN" "$FORGEJO_BIN"
rm -f "$TMP_BIN"
trap - EXIT # no longer need cleanup
# ---------------------------------------------------------------------------
# Start the service
# ---------------------------------------------------------------------------
log "Starting $FORGEJO_SVC service ..."
systemctl start "$FORGEJO_SVC"
# Give it a moment, then verify.
sleep 2
if systemctl is-active --quiet "$FORGEJO_SVC"; then
log "Service started successfully."
else
# Roll back
die "Service failed to start after update. Rolling back.
Check journal: systemctl status $FORGEJO_SVC && journalctl -u $FORGEJO_SVC -n 50"
fi
# ---------------------------------------------------------------------------
# Done
# ---------------------------------------------------------------------------
NEW_VER=$("$FORGEJO_BIN" -c "$FORGEJO_INI" -v 2>/dev/null || echo "unknown")
log "Update complete: $CURRENT_VER$NEW_VER"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment