From 1c16efd8726f43d162cbf7edefbaddc8ae624e20 Mon Sep 17 00:00:00 2001 From: jarek Date: Mon, 2 Mar 2026 13:10:03 +0100 Subject: [PATCH] v1.0.20 --- Dockerfile | 5 +++-- src/lib/data/changelog.json | 3 ++- src/lib/server/subprocess-manager.ts | 27 +++++++++++++++++++++++++-- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index f37da8d..d1c409f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -90,10 +90,11 @@ RUN rm -rf node_modules \ && rm -rf node_modules/@types # Build Go collector -FROM golang:1.24 AS go-builder +FROM --platform=$BUILDPLATFORM golang:1.24 AS go-builder +ARG TARGETARCH WORKDIR /app COPY collector/ ./collector/ -RUN cd collector && CGO_ENABLED=0 go build -o /app/bin/collection-worker . +RUN cd collector && CGO_ENABLED=0 GOARCH=$TARGETARCH go build -o /app/bin/collection-worker . # ----------------------------------------------------------------------------- # Stage 3: Final Image (Scratch + Custom Wolfi OS) diff --git a/src/lib/data/changelog.json b/src/lib/data/changelog.json index 57ff30c..3bdec1f 100644 --- a/src/lib/data/changelog.json +++ b/src/lib/data/changelog.json @@ -3,7 +3,8 @@ "version": "1.0.20", "date": "2026-03-02", "changes": [ - { "type": "fix", "text": "regression on Synology DSM" } + { "type": "fix", "text": "regression on Synology DSM" }, + { "type": "fix", "text": "Fix ARM64 regression: Go collector crashing on Raspberry Pi and other ARM devices" } ], "imageTag": "fnsys/dockhand:v1.0.20" }, diff --git a/src/lib/server/subprocess-manager.ts b/src/lib/server/subprocess-manager.ts index d14c4c1..4b0f487 100644 --- a/src/lib/server/subprocess-manager.ts +++ b/src/lib/server/subprocess-manager.ts @@ -84,6 +84,10 @@ let lineBuffer: Buffer = Buffer.alloc(0); let restartDelay = 1000; const MAX_RESTART_DELAY = 60000; +// Ready-signal plumbing: resolved when Go sends {"type":"ready"} +let readyResolve: (() => void) | null = null; +let readyPromise: Promise | null = null; + // Dedup cache for events const recentEvents: Map = new Map(); // Disk warning cooldown per env @@ -147,6 +151,8 @@ function handleLine(line: string): void { case 'ready': console.log('[SubprocessManager] Go worker ready'); restartDelay = 1000; // Reset backoff on successful start + readyResolve?.(); + readyResolve = null; break; case 'metrics': @@ -531,6 +537,9 @@ export async function startSubprocesses(): Promise { const workerPath = resolveWorkerPath(); console.log(`[SubprocessManager] Starting Go worker (${workerPath})...`); + // Set up ready promise BEFORE spawning so we don't miss the signal + readyPromise = new Promise(resolve => { readyResolve = resolve; }); + proc = spawn(workerPath, [], { stdio: ['pipe', 'pipe', 'inherit'] }); @@ -540,6 +549,10 @@ export async function startSubprocesses(): Promise { // Handle process exit proc.on('exit', (code) => { + // Clear stale ready promise if process exits before signalling ready + readyResolve = null; + readyPromise = null; + if (!isShuttingDown) { console.warn(`[SubprocessManager] Go worker exited with code ${code}, restarting in ${restartDelay / 1000}s...`); proc = null; @@ -554,8 +567,18 @@ export async function startSubprocesses(): Promise { proc = null; }); - // Wait a moment for the process to start, then send configs - await new Promise(resolve => setTimeout(resolve, 100)); + // Wait for Go to signal it's ready and reading stdin, then send configs. + // This fixes a race on DietPi where stdin closes transiently before the + // old blind 100ms wait ends, causing configure messages to be silently dropped. + try { + await Promise.race([ + readyPromise, + new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5000)) + ]); + } catch { + console.warn('[SubprocessManager] Go worker ready timeout, sending configs anyway'); + } + readyPromise = null; await sendEnvironmentConfigs(); // Start dedup cleanup interval