From 87a5ed950d377a04c9a7f54c75d9d389c17ee799 Mon Sep 17 00:00:00 2001 From: Jarek Krochmalski Date: Sat, 12 Sep 2026 16:24:13 +0200 Subject: [PATCH] self-update no longer fails at the updater step on baseline (old x86_64) images --- src/lib/server/updater-image-core.ts | 14 ++++++++++++++ src/routes/api/self-update/+server.ts | 6 +++++- tests/updater-image-core.test.ts | 18 ++++++++++++++++++ updater/Dockerfile.baseline | 26 ++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 src/lib/server/updater-image-core.ts create mode 100644 tests/updater-image-core.test.ts create mode 100644 updater/Dockerfile.baseline diff --git a/src/lib/server/updater-image-core.ts b/src/lib/server/updater-image-core.ts new file mode 100644 index 0000000..5152565 --- /dev/null +++ b/src/lib/server/updater-image-core.ts @@ -0,0 +1,14 @@ +/** + * Which dockhand-updater sidecar image self-update should launch. + * + * The default updater is Wolfi-based (compiled for x86-64-v2). On old x86_64 CPUs + * without v2 - the same hardware that runs the baseline Dockhand image + * (DOCKHAND_VARIANT=baseline) - the Wolfi updater can't exec and Docker reports exit + * 127 at "Launching updater", so self-update is broken. Those hosts must pull the + * `-baseline` (Alpine/musl) updater instead, mirroring the backup helper's variant + * split (see restic.ts HELPER_VARIANT_SUFFIX). + */ +export function updaterImageForVariant(variant: string | undefined): string { + const suffix = variant === 'baseline' ? '-baseline' : ''; + return `fnsys/dockhand-updater:latest${suffix}`; +} diff --git a/src/routes/api/self-update/+server.ts b/src/routes/api/self-update/+server.ts index a33764b..652e371 100644 --- a/src/routes/api/self-update/+server.ts +++ b/src/routes/api/self-update/+server.ts @@ -12,8 +12,12 @@ import { import { buildRegistryAuthHeader, unixSocketRequest, unixSocketStreamRequest } from '$lib/server/docker'; import type { RequestHandler } from './$types'; import { prefersJSON, sseToJSON } from '$lib/server/sse'; +import { updaterImageForVariant } from '$lib/server/updater-image-core'; -const UPDATER_IMAGE = 'fnsys/dockhand-updater:latest'; +// Baseline Dockhand (old x86_64 without v2) needs the Alpine/musl updater; the Wolfi +// default can't exec there (exit 127 at "Launching updater"). Mirrors the backup +// helper's variant split. +const UPDATER_IMAGE = updaterImageForVariant(process.env.DOCKHAND_VARIANT); const UPDATER_LABEL = 'dockhand.updater'; /** Get TCP Docker host if configured, null otherwise. */ diff --git a/tests/updater-image-core.test.ts b/tests/updater-image-core.test.ts new file mode 100644 index 0000000..1140e1d --- /dev/null +++ b/tests/updater-image-core.test.ts @@ -0,0 +1,18 @@ +import { describe, it, expect } from 'bun:test'; +import { updaterImageForVariant } from '../src/lib/server/updater-image-core'; + +describe('updaterImageForVariant', () => { + it('uses the -baseline updater for the baseline Dockhand variant (old x86_64 without v2)', () => { + expect(updaterImageForVariant('baseline')).toBe('fnsys/dockhand-updater:latest-baseline'); + }); + + it('uses the default updater for the normal (Wolfi) variant', () => { + expect(updaterImageForVariant(undefined)).toBe('fnsys/dockhand-updater:latest'); + expect(updaterImageForVariant('')).toBe('fnsys/dockhand-updater:latest'); + }); + + it('only the exact "baseline" value switches variant (no accidental match)', () => { + expect(updaterImageForVariant('Baseline')).toBe('fnsys/dockhand-updater:latest'); + expect(updaterImageForVariant('baseline-x')).toBe('fnsys/dockhand-updater:latest'); + }); +}); diff --git a/updater/Dockerfile.baseline b/updater/Dockerfile.baseline new file mode 100644 index 0000000..c3f8f1e --- /dev/null +++ b/updater/Dockerfile.baseline @@ -0,0 +1,26 @@ +# syntax=docker/dockerfile:1.4 +# Dockhand Updater - BASELINE build (Alpine/musl, amd64 only) +# +# The normal updater (updater/Dockerfile) is Wolfi-based; Wolfi compiles its busybox +# and docker-cli for x86-64-v2 (SSE4.2/POPCNT). On older x86_64 CPUs without those +# instructions (TrueNAS, old Intel Atom/Celeron - the same hardware that needs the +# baseline Dockhand image) the Wolfi updater can't exec at all: the entrypoint's shell +# dies with "CPU does not support x86-64-v2", which Docker reports as exit 127 at the +# "Launching updater" step, so self-update is completely broken for baseline users. +# +# Alpine's musl busybox + docker-cli run on x86-64-v1, so the baseline updater is a +# plain Alpine image running the same /update.sh. amd64 only - baseline is a workaround +# for old x86_64 hardware, not an ARM concern. +# +# Rebuild trigger: the pipeline is change-gated on updater/, so touching this file +# forces a rebuild of the -baseline tag. +FROM alpine:3.21 + +# docker-cli (the stop/rm/rename/network/start the sidecar does) + busybox is the base +# Alpine shell, tar and date. That covers everything /update.sh needs. +RUN apk add --no-cache docker-cli + +COPY update.sh /update.sh +RUN chmod +x /update.sh + +ENTRYPOINT ["/update.sh"]