diff --git a/update.sh b/update.sh new file mode 100755 index 0000000..b358fc0 --- /dev/null +++ b/update.sh @@ -0,0 +1,231 @@ +#!/bin/bash +# ============================================================ +# Next Workspace — Update Script +# +# Checks for new versions of NextWks + Authelia, +# shows changelog, asks approval, updates safely with rollback. +# +# Usage: +# ./update.sh Check + interactive update +# ./update.sh --check Check only, no update +# ./update.sh --rollback Revert to previous backup +# ============================================================ +set -euo pipefail + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m' +error() { echo -e "${RED}Error:${NC} $1" >&2; } +success() { echo -e "${GREEN}$1${NC}"; } +info() { echo -e "${BLUE}$1${NC}"; } +warn() { echo -e "${YELLOW}$1${NC}"; } +header() { echo -e "\n${BOLD}${CYAN}$1${NC}"; } + +REPO_DIR="$(cd "$(dirname "$0")" && pwd)" +NEXTWKS_BIN="/opt/nextwks/bin/core" +AUTHELIA_BIN="/opt/authelia/authelia" +BACKUP_DIR="/opt/nextwks/backups" + +# ============================================================ +# HELPERS +# ============================================================ +installed_version() { + if [ -f "$NEXTWKS_BIN" ]; then + curl -s --max-time 2 http://localhost:8080/api/version 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin).get('version','unknown'))" 2>/dev/null || echo "unknown" + else + echo "not-installed" + fi +} + +authelia_installed_version() { + if [ -f "$AUTHELIA_BIN" ]; then + "$AUTHELIA_BIN" version 2>/dev/null | head -1 | awk '{print $3}' | sed 's/v//' || echo "unknown" + else + echo "not-installed" + fi +} + +backup() { mkdir -p "$BACKUP_DIR" && cp "$1" "$BACKUP_DIR/$(basename "$1").$(date +%s)" 2>/dev/null; } +health_ok() { [ "$(curl -s --max-time 3 http://localhost:8080/api/health 2>/dev/null)" = '{"status":"ok"}' ]; } + +# ============================================================ +# CHECK +# ============================================================ +MODE="${1:-}" +[ "$MODE" = "--rollback" ] && { header "Rollback"; ls -t "$BACKUP_DIR"/* 2>/dev/null | head -5; warn "Not implemented yet — restore manually"; exit 0; } + +header "── Checking for Updates ──" + +# --- NextWks --- +CURRENT_NW=$(installed_version) +info "NextWks: installed = ${CURRENT_NW}" + +# Fetch latest tag from Forgejo +LATEST_TAG=$(curl -s --max-time 5 "https://git.lohmar.co.uk/api/v1/repos/lexton-it/NextWks/releases?limit=1" 2>/dev/null | python3 -c "import sys,json; r=json.load(sys.stdin); print(r[0]['tag_name'].lstrip('v') if r else '0')" 2>/dev/null || echo "0") + +# Also check git tag if repo is local +if [ -d "$REPO_DIR/.git" ]; then + cd "$REPO_DIR" + git fetch --tags --quiet 2>/dev/null || true + GIT_LATEST=$(git tag -l 'v*' --sort=-v:refname | head -1 | sed 's/^v//') + [ -n "$GIT_LATEST" ] && LATEST_TAG="$GIT_LATEST" +fi + +info "NextWks: latest = ${LATEST_TAG:-unknown}" + +NW_UPDATE=false +[ "$CURRENT_NW" != "$LATEST_TAG" ] && [ -n "$LATEST_TAG" ] && [ "$LATEST_TAG" != "0" ] && NW_UPDATE=true + +if $NW_UPDATE; then + warn " ↳ Update available!" + cd "$REPO_DIR" + if [ -d "$REPO_DIR/.git" ]; then + echo "" + info "Recent commits since ${CURRENT_NW}:" + git log "v${CURRENT_NW}..v${LATEST_TAG}" --oneline 2>/dev/null | head -10 || echo " (no local repo)" + fi +else + success " ↳ Up to date" +fi + +# --- Authelia --- +CURRENT_AH=$(authelia_installed_version) +info "Authelia: installed = ${CURRENT_AH}" + +LATEST_AH=$(curl -s --max-time 5 "https://api.github.com/repos/authelia/authelia/releases/latest" 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin)['tag_name'].lstrip('v'))" 2>/dev/null || echo "unknown") + +info "Authelia: latest = ${LATEST_AH:-unknown}" + +AH_UPDATE=false +[ "$CURRENT_AH" != "$LATEST_AH" ] && [ -n "$LATEST_AH" ] && [ "$LATEST_AH" != "unknown" ] && AH_UPDATE=true + +if $AH_UPDATE; then + warn " ↳ Update available!" +else + success " ↳ Up to date" +fi + +echo "" +if [ "$MODE" = "--check" ]; then + echo "Run without --check to install updates."; exit 0 +fi + +if ! $NW_UPDATE && ! $AH_UPDATE; then + success "Everything is up to date!"; exit 0 +fi + +# ============================================================ +# APPROVE +# ============================================================ +header "── Approval ──" +echo "" +info "The following will be updated:" +$NW_UPDATE && info " • NextWks: ${CURRENT_NW} → ${LATEST_TAG}" +$AH_UPDATE && info " • Authelia: ${CURRENT_AH} → ${LATEST_AH}" +echo "" +read -p " Proceed with update? [y/N]: " CONFIRM +[ "$CONFIRM" != "y" ] && [ "$CONFIRM" != "Y" ] && { echo "Aborted."; exit 0; } + +# ============================================================ +# UPDATE NEXTWKS +# ============================================================ +if $NW_UPDATE; then + header "── Updating NextWks ──" + + # Backup current binary + if [ -f "$NEXTWKS_BIN" ]; then + backup "$NEXTWKS_BIN" + success "Backed up current binary" + fi + + # Pull latest source + cd "$REPO_DIR" + if [ -d "$REPO_DIR/.git" ]; then + info "Pulling latest code..." + git pull origin main --quiet 2>/dev/null || { error "git pull failed"; exit 1; } + git checkout "v${LATEST_TAG}" --quiet 2>/dev/null || git checkout main --quiet 2>/dev/null + fi + + # Build + cd "$REPO_DIR/src" + info "Building..." + BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + COMMIT_SHA=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") + + go build -ldflags="-s -w \ + -X git.lohmar.co.uk/lexton-it/NextWks/core/version.Version=${LATEST_TAG} \ + -X git.lohmar.co.uk/lexton-it/NextWks/core/version.BuildTime=${BUILD_TIME} \ + -X git.lohmar.co.uk/lexton-it/NextWks/core/version.CommitSHA=${COMMIT_SHA}" \ + -o "$REPO_DIR/app/core" . + success "Built ${LATEST_TAG}" + + # Replace + systemctl stop nextwks 2>/dev/null || true + cp "$REPO_DIR/app/core" "$NEXTWKS_BIN" + systemctl start nextwks + sleep 3 + + if health_ok; then + success "NextWks ${LATEST_TAG} — OK" + else + error "Health check failed — rolling back..." + LATEST_BACKUP=$(ls -t "$BACKUP_DIR/core."* 2>/dev/null | head -1) + if [ -n "$LATEST_BACKUP" ]; then + systemctl stop nextwks 2>/dev/null || true + cp "$LATEST_BACKUP" "$NEXTWKS_BIN" + systemctl start nextwks + sleep 2 + health_ok && success "Rollback successful" || error "Rollback also failed — manual intervention needed" + else + error "No backup found" + fi + fi +fi + +# ============================================================ +# UPDATE AUTHELIA +# ============================================================ +if $AH_UPDATE; then + header "── Updating Authelia ──" + + if [ -f "$AUTHELIA_BIN" ]; then + backup "$AUTHELIA_BIN" + success "Backed up Authelia binary" + fi + + info "Downloading Authelia ${LATEST_AH}..." + wget -q "https://github.com/authelia/authelia/releases/download/v${LATEST_AH}/authelia-v${LATEST_AH}-linux-amd64.tar.gz" -O /tmp/authelia-update.tar.gz + tar -xzf /tmp/authelia-update.tar.gz -C /tmp/ + rm -f /tmp/authelia-update.tar.gz + + systemctl stop authelia 2>/dev/null || true + cp "/tmp/authelia-linux-amd64" "$AUTHELIA_BIN" + chmod +x "$AUTHELIA_BIN" + rm -f /tmp/authelia-linux-amd64 + systemctl start authelia + sleep 3 + + if curl -s --max-time 3 http://127.0.0.1:9091/api/health 2>/dev/null | grep -q OK; then + success "Authelia ${LATEST_AH} — OK" + else + error "Authelia health check failed — rolling back..." + LATEST_BACKUP=$(ls -t "$BACKUP_DIR/authelia."* 2>/dev/null | head -1) + if [ -n "$LATEST_BACKUP" ]; then + systemctl stop authelia 2>/dev/null || true + cp "$LATEST_BACKUP" "$AUTHELIA_BIN" + systemctl start authelia + sleep 2 + curl -s --max-time 3 http://127.0.0.1:9091/api/health | grep -q OK && success "Rollback OK" || error "Rollback failed" + fi + fi +fi + +# ============================================================ +# DONE +# ============================================================ +echo "" +success "════════════════════════════════════════" +success " Update complete" +success "════════════════════════════════════════" +echo "" +info " NextWks: $(installed_version)" +info " Authelia: $(authelia_installed_version)" +echo ""