#!/usr/bin/env bash # ============================================================================= # update-fxserver.sh -- v1.1.0 # # Aktualisiert einen FiveM-FXServer auf die aktuelle Linux-Artefakt-Version. # # Ablauf: # 1. Version ueber die Cfx-Changelog-API ermitteln # 2. Artefakt nach /tmp herunterladen und entpacken (Server laeuft weiter) # 3. Dienst stoppen, altes alpine/ + run.sh beiseiteschieben # 4. Neues Artefakt einspielen, Besitzer setzen, Dienst starten # 5. Bleibt der Dienst nicht oben, wird der alte Stand zurueckgeholt # # Ersetzt werden ausschliesslich alpine/ und run.sh im Artefakt-Ordner. # server-data/ mit resources/, server.cfg und cache/ wird nie angefasst. # # Aufruf als root: sudo ./update-fxserver.sh # Doku: https://www.musiker15.de/de/docs/fivem/auto-update # ============================================================================= set -euo pipefail # ── Konfiguration ───────────────────────────────────────────────────────────── # Diese fuenf Werte anpassen, sonst nichts. FX_DIR="/opt/fivem/server" # Artefakt-Ordner (enthaelt run.sh + alpine/) FX_USER="fivem" # Besitzer der Dateien FX_GROUP="fivem" # Gruppe SERVICE="fivem" # Name der systemd-Unit ohne .service CHANNEL="recommended" # "recommended" (Produktivsystem) oder "latest" # Wie lange nach dem Start gewartet wird, bevor der Lauf als erfolgreich gilt. # Ein FXServer, der an einer kaputten Ressource scheitert, braucht ein paar # Sekunden bis dorthin. Zu kurz gewaehlt meldet das Script faelschlich Erfolg. HEALTH_WAIT="15" API="https://changelogs-live.fivem.net/api/changelog/versions/linux/server" # ────────────────────────────────────────────────────────────────────────────── log() { printf '\033[1;32m[+]\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m[!]\033[0m %s\n' "$*"; } err() { printf '\033[1;31m[x]\033[0m %s\n' "$*" >&2; } die() { err "$*"; exit 1; } log "=== update-fxserver.sh $(date '+%Y-%m-%d %H:%M:%S') ===" # ── Vorbedingungen ──────────────────────────────────────────────────────────── [[ $EUID -eq 0 ]] || die "Bitte als root ausfuehren: sudo $0" for bin in curl tar systemctl; do command -v "$bin" >/dev/null 2>&1 || die "Benoetigtes Programm fehlt: $bin" done [[ -d "$FX_DIR" ]] || die "Artefakt-Verzeichnis nicht gefunden: $FX_DIR" id "$FX_USER" >/dev/null 2>&1 || die "Benutzer existiert nicht: $FX_USER" getent group "$FX_GROUP" >/dev/null 2>&1 || die "Gruppe existiert nicht: $FX_GROUP" case "$CHANNEL" in recommended|latest) ;; *) die "CHANNEL muss 'recommended' oder 'latest' sein, ist: '$CHANNEL'" ;; esac # ── Version ermitteln ───────────────────────────────────────────────────────── log "Frage FXServer-Version ab (Kanal: ${CHANNEL}) ..." JSON="$(curl -fsSL --retry 3 --retry-delay 5 "$API")" \ || die "Changelog-API nicht erreichbar: $API" if command -v jq >/dev/null 2>&1; then DOWNLOAD_URL="$(printf '%s' "$JSON" | jq -r ".${CHANNEL}_download // empty")" VERSION="$( printf '%s' "$JSON" | jq -r ".${CHANNEL} // empty")" else warn "jq nicht installiert, nutze grep-Rueckfallweg (besser: apt install jq)." echo | grep -qP '' 2>/dev/null \ || die "grep ohne PCRE-Unterstuetzung. Bitte 'apt install jq' ausfuehren." DOWNLOAD_URL="$(printf '%s' "$JSON" | grep -oP "\"${CHANNEL}_download\"\s*:\s*\"\K[^\"]+")" VERSION="$( printf '%s' "$JSON" | grep -oP "\"${CHANNEL}\"\s*:\s*\"\K[^\"]+" | head -n1)" fi [[ -n "${DOWNLOAD_URL:-}" && "$DOWNLOAD_URL" == https://* ]] \ || die "Download-URL konnte nicht ermittelt werden." log "Version : ${VERSION:-unbekannt}" log "URL : $DOWNLOAD_URL" # ── Bereits aktuell? ────────────────────────────────────────────────────────── STAMP="${FX_DIR}/.installed-version" if [[ -f "$STAMP" && -n "${VERSION:-}" ]] && [[ "$(cat "$STAMP")" == "$VERSION" ]]; then log "Version ${VERSION} ist bereits installiert. Nichts zu tun." exit 0 fi # ── Temporaeres Verzeichnis, raeumt sich selbst auf ─────────────────────────── TMP="$(mktemp -d /tmp/fxserver-update.XXXXXX)" trap 'rm -rf "$TMP"' EXIT ARCHIVE="$TMP/fx.tar.xz" STAGING="$TMP/staging" mkdir -p "$STAGING" # ── Herunterladen und entpacken, BEVOR der Dienst angefasst wird ────────────── # Schlaegt hier etwas fehl, laeuft der Server einfach weiter. log "Lade Artefakt herunter ..." curl -fSL --retry 3 --retry-delay 5 -o "$ARCHIVE" "$DOWNLOAD_URL" \ || die "Download fehlgeschlagen." [[ -s "$ARCHIVE" ]] || die "Heruntergeladene Datei ist leer." log "Entpacke Archiv ..." tar -xJf "$ARCHIVE" -C "$STAGING" || die "Entpacken fehlgeschlagen." # Die Archivstruktur hat sich in der Vergangenheit geaendert, mal mit # Wrapper-Ordner, mal ohne. Deshalb run.sh suchen statt den Pfad zu raten. RUNSH_PATH="$(find "$STAGING" -maxdepth 2 -name run.sh -type f | head -n1)" [[ -n "$RUNSH_PATH" ]] || die "run.sh im Archiv nicht gefunden, Struktur unerwartet." SRC_ROOT="$(dirname "$RUNSH_PATH")" [[ -d "$SRC_ROOT/alpine" ]] || die "alpine/ im Archiv nicht gefunden." # ── Dienst stoppen ──────────────────────────────────────────────────────────── if systemctl is-active --quiet "$SERVICE"; then log "Stoppe '${SERVICE}' ..." systemctl stop "$SERVICE" else warn "'${SERVICE}' war nicht aktiv." fi # ── Altes Artefakt beiseiteschieben statt loeschen ──────────────────────────── BACKUP="${FX_DIR}/.previous" rm -rf "$BACKUP" mkdir -p "$BACKUP" [[ -d "$FX_DIR/alpine" ]] && mv "$FX_DIR/alpine" "$BACKUP/alpine" [[ -f "$FX_DIR/run.sh" ]] && mv "$FX_DIR/run.sh" "$BACKUP/run.sh" restore() { err "Update fehlgeschlagen, stelle das vorherige Artefakt wieder her." rm -rf "$FX_DIR/alpine" rm -f "$FX_DIR/run.sh" [[ -d "$BACKUP/alpine" ]] && mv "$BACKUP/alpine" "$FX_DIR/alpine" [[ -f "$BACKUP/run.sh" ]] && mv "$BACKUP/run.sh" "$FX_DIR/run.sh" chown -R "${FX_USER}:${FX_GROUP}" "$FX_DIR" || true systemctl start "$SERVICE" || true err "Alter Stand ist zurueck. Log pruefen: journalctl -u ${SERVICE} -n 50" exit 1 } # ── Neues Artefakt einspielen ───────────────────────────────────────────────── log "Tausche alpine/ und run.sh in ${FX_DIR} ..." cp -a "$SRC_ROOT/alpine" "$FX_DIR/alpine" || restore cp -a "$SRC_ROOT/run.sh" "$FX_DIR/run.sh" || restore chmod +x "$FX_DIR/run.sh" [[ -f "$FX_DIR/alpine/opt/cfx-server/FXServer" ]] \ && chmod +x "$FX_DIR/alpine/opt/cfx-server/FXServer" log "Setze Besitzer auf ${FX_USER}:${FX_GROUP} ..." chown -R "${FX_USER}:${FX_GROUP}" "$FX_DIR" || restore # ── Starten und nachsehen, ob er auch oben bleibt ───────────────────────────── log "Starte '${SERVICE}' neu ..." systemctl restart "$SERVICE" || restore log "Warte ${HEALTH_WAIT}s und pruefe, ob der Dienst oben bleibt ..." sleep "$HEALTH_WAIT" if systemctl is-active --quiet "$SERVICE"; then printf '%s' "${VERSION:-unbekannt}" > "$STAMP" rm -rf "$BACKUP" log "Fertig. FXServer ${VERSION:-?} laeuft." else err "'${SERVICE}' laeuft nach dem Neustart NICHT." restore fi