From 3b179075a8d978361d59541f0952a2e4c2214824 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Mon, 25 May 2026 11:41:52 +0000 Subject: [PATCH] fix(upgrade): keep self-upgrades alive after stopping the service --- changelog.md | 9 ++++ install.sh | 92 ++++++++++++++++++++---------------- ts/classes/update-manager.ts | 42 +++++++++++----- ts_interfaces/data/system.ts | 3 +- 4 files changed, 93 insertions(+), 53 deletions(-) diff --git a/changelog.md b/changelog.md index 5b479a0..cd99fda 100644 --- a/changelog.md +++ b/changelog.md @@ -2,7 +2,16 @@ ## Pending +### Fixes +- keep self-upgrades alive after stopping the Onebox service + - Launch dashboard-triggered upgrades as transient systemd units outside the onebox.service cgroup. + - Download and validate installer binaries before stopping the running service. + - Restart the previous service if installation fails after the service was stopped. +- keep self-upgrades alive after stopping the service (upgrade) + - Launch dashboard-triggered upgrades as transient systemd units outside the service cgroup. + - Download and validate installer binaries before stopping the running service. + - Restart the previous service if installation fails after it was stopped. ## 2026-05-25 - 2.1.1 diff --git a/install.sh b/install.sh index f89230e..04b72ab 100755 --- a/install.sh +++ b/install.sh @@ -170,14 +170,55 @@ DOWNLOAD_URL="${GITEA_BASE_URL}/${GITEA_REPO}/releases/download/${VERSION}/${BIN echo "Download URL: $DOWNLOAD_URL" echo "" -# Check if service is running and stop it +# Check whether the service should be restarted after a successful install. SERVICE_WAS_RUNNING=0 if systemctl is-enabled --quiet "$SERVICE_NAME" 2>/dev/null || systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then SERVICE_WAS_RUNNING=1 - if systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then - echo "Stopping Onebox service..." - systemctl stop "$SERVICE_NAME" +fi + +# Download and validate the new binary before touching the running service. +echo "Downloading Onebox binary..." +TEMP_DIR=$(mktemp -d) +TEMP_FILE="$TEMP_DIR/$BINARY_NAME" +cleanup_temp() { + rm -rf "$TEMP_DIR" +} +trap cleanup_temp EXIT + +if ! curl -fSL "$DOWNLOAD_URL" -o "$TEMP_FILE"; then + echo "Error: Failed to download binary from $DOWNLOAD_URL" + echo "" + echo "Please check:" + echo " 1. Your internet connection" + echo " 2. The specified version exists: ${GITEA_BASE_URL}/${GITEA_REPO}/releases" + echo " 3. The platform binary is available for this release" + exit 1 +fi + +if [ ! -s "$TEMP_FILE" ]; then + echo "Error: Downloaded file is empty or does not exist" + exit 1 +fi + +chmod +x "$TEMP_FILE" +if ! "$TEMP_FILE" --version >/dev/null 2>&1; then + echo "Error: Downloaded file is not an executable Onebox binary" + exit 1 +fi + +SERVICE_STOPPED=0 +restart_previous_service_on_error() { + if [ $SERVICE_STOPPED -eq 1 ]; then + echo "Installation failed after stopping Onebox; restarting previous service..." + systemctl start "$SERVICE_NAME" || true fi +} +trap 'restart_previous_service_on_error; cleanup_temp' ERR + +if [ $SERVICE_WAS_RUNNING -eq 1 ] && systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then + echo "Stopping Onebox service..." + systemctl stop "$SERVICE_NAME" + SERVICE_STOPPED=1 fi # Clean installation directory - ensure only binary exists @@ -190,44 +231,10 @@ fi echo "Creating installation directory: $INSTALL_DIR" mkdir -p "$INSTALL_DIR" -# Download binary -echo "Downloading Onebox binary..." -TEMP_FILE="$INSTALL_DIR/onebox.download" -curl -sSL "$DOWNLOAD_URL" -o "$TEMP_FILE" - -if [ $? -ne 0 ]; then - echo "Error: Failed to download binary from $DOWNLOAD_URL" - echo "" - echo "Please check:" - echo " 1. Your internet connection" - echo " 2. The specified version exists: ${GITEA_BASE_URL}/${GITEA_REPO}/releases" - echo " 3. The platform binary is available for this release" - rm -f "$TEMP_FILE" - exit 1 -fi - -# Check if download was successful (file exists and not empty) -if [ ! -s "$TEMP_FILE" ]; then - echo "Error: Downloaded file is empty or does not exist" - rm -f "$TEMP_FILE" - exit 1 -fi - -# Move to final location +# Install binary BINARY_PATH="$INSTALL_DIR/onebox" -mv "$TEMP_FILE" "$BINARY_PATH" - -if [ $? -ne 0 ] || [ ! -f "$BINARY_PATH" ]; then - echo "Error: Failed to move binary to $BINARY_PATH" - rm -f "$TEMP_FILE" 2>/dev/null - exit 1 -fi - -# Make executable -chmod +x "$BINARY_PATH" - -if [ $? -ne 0 ]; then - echo "Error: Failed to make binary executable" +if ! install -m 0755 "$TEMP_FILE" "$BINARY_PATH" || [ ! -f "$BINARY_PATH" ]; then + echo "Error: Failed to install binary to $BINARY_PATH" exit 1 fi @@ -256,10 +263,13 @@ if [ $SERVICE_WAS_RUNNING -eq 1 ]; then onebox systemd enable echo "Restarting Onebox service..." systemctl restart "$SERVICE_NAME" + SERVICE_STOPPED=0 echo "Service restarted successfully." echo "" fi +trap - ERR + echo "================================================" echo " Onebox Installation Complete!" echo "================================================" diff --git a/ts/classes/update-manager.ts b/ts/classes/update-manager.ts index e09c4d9..7fbc2e8 100644 --- a/ts/classes/update-manager.ts +++ b/ts/classes/update-manager.ts @@ -66,24 +66,43 @@ export class OneboxUpdateManager { }; } - const command = new Deno.Command('bash', { - args: ['-c', this.createDetachedUpgradeScript()], + const unitName = `onebox-upgrade-${Date.now()}`; + const command = new Deno.Command('systemd-run', { + args: [ + '--unit', + unitName, + '--description', + `Onebox upgrade to ${targetVersion}`, + '--collect', + '--property=Type=oneshot', + '--setenv=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', + 'bash', + '-lc', + this.createDetachedUpgradeScript(), + ], stdin: 'null', - stdout: 'null', - stderr: 'null', - detached: true, + stdout: 'piped', + stderr: 'piped', }); - const child = command.spawn(); - child.unref(); + const result = await command.output(); + if (!result.success) { + const stderr = new TextDecoder().decode(result.stderr).trim(); + const stdout = new TextDecoder().decode(result.stdout).trim(); + throw new Error( + `Failed to start Onebox upgrade systemd unit: ${ + stderr || stdout || `exit code ${result.code}` + }`, + ); + } this.upgradeStartedAt = Date.now(); - logger.info(`Started detached Onebox upgrade process ${child.pid}`); + logger.info(`Started Onebox upgrade systemd unit ${unitName}`); return { accepted: true, currentVersion: status.currentVersion, targetVersion, message: 'Onebox upgrade started. The service will restart automatically.', - pid: child.pid, + unitName, logPath: UPGRADE_LOG_PATH, }; } @@ -107,7 +126,7 @@ export class OneboxUpdateManager { } const installCommand = new Deno.Command('bash', { - args: ['-c', `curl -sSL ${ONEBOX_INSTALL_SCRIPT_URL} | bash`], + args: ['-c', `set -o pipefail; curl -fsSL ${ONEBOX_INSTALL_SCRIPT_URL} | bash`], stdin: 'inherit', stdout: 'inherit', stderr: 'inherit', @@ -202,11 +221,12 @@ export class OneboxUpdateManager { private createDetachedUpgradeScript(): string { return ` set -e +set -o pipefail mkdir -p /var/log { echo "==== Onebox upgrade started $(date -Is) ====" sleep 2 - curl -sSL ${ONEBOX_INSTALL_SCRIPT_URL} | bash + curl -fsSL ${ONEBOX_INSTALL_SCRIPT_URL} | bash echo "==== Onebox upgrade finished $(date -Is) ====" } >> ${UPGRADE_LOG_PATH} 2>&1 `; diff --git a/ts_interfaces/data/system.ts b/ts_interfaces/data/system.ts index 0ea7c58..d466137 100644 --- a/ts_interfaces/data/system.ts +++ b/ts_interfaces/data/system.ts @@ -2,7 +2,7 @@ * System status data shapes for Onebox */ -import type { TPlatformServiceType, TPlatformServiceStatus } from './platform.ts'; +import type { TPlatformServiceStatus, TPlatformServiceType } from './platform.ts'; export interface IOneboxUpdateStatus { currentVersion: string; @@ -20,6 +20,7 @@ export interface IOneboxUpgradeStartResult { targetVersion: string; message: string; pid?: number; + unitName?: string; logPath?: string; }