Files
felhom-controller/controller/internal/web/netprobe_linux.go
T
admin bb8737a81f netstorage: verify-before-commit orchestration — agentapi verify fields, uid-1000 re-exec probe, detached add job, orphan rows
Controller half of the verify pipeline (SPIKE-nas-verify b57f6c1): AddNetStorage
gains verify/job fields + typed NetAddRefusedError; NetVerifyStatus polls the
agent slot; --netprobe hidden re-exec mode (SysProcAttr.Credential uid/gid 1000,
no shell) proves in-guest writability; the add handler starts a detached
single-flight job (agent_add → verifying → probing → registering LAST) with full
rollback on any failure incl. verify-lost-after-restart (Scenario F); §3.2
Hungarian error map server-side; live-but-unregistered shares surface as remove-
only 'Árva megosztás' rows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-11 09:59:33 +02:00

44 lines
1.6 KiB
Go

//go:build linux
package web
import (
"context"
"os"
"os/exec"
"strings"
"syscall"
"time"
)
// netProbeTimeout bounds one probe run (LAN write+readback is sub-second; a wedged share must not
// hold the orchestrator — the NFS soft/retry=0 options error out well inside this).
const netProbeTimeout = 30 * time.Second
// runNetProbe re-execs this binary as `felhom-controller --netprobe <dir>` with uid/gid 1000
// credentials (supplementary groups CLEARED — the probe must see exactly what a media app sees)
// and maps the exit code to a verdict. This is the production netProbeFn seam value.
func runNetProbe(ctx context.Context, dir string) probeOutcome {
exe, err := os.Executable()
if err != nil {
return probeOutcome{OK: false, Category: "probe_io", Detail: "probe re-exec: executable path: " + err.Error()}
}
pctx, cancel := context.WithTimeout(ctx, netProbeTimeout)
defer cancel()
cmd := exec.CommandContext(pctx, exe, "--netprobe", dir)
cmd.SysProcAttr = &syscall.SysProcAttr{
Credential: &syscall.Credential{Uid: 1000, Gid: 1000, Groups: []uint32{}},
}
out, err := cmd.CombinedOutput()
output := strings.TrimSpace(string(out))
if err != nil {
if ee, ok := err.(*exec.ExitError); ok {
return netProbeVerdict(ee.ExitCode(), output)
}
// Spawn-level failure (EPERM on setuid = capability dropped somewhere — spike Q2 proved the
// default container HAS the caps, so this is a real config regression worth the raw detail).
return probeOutcome{OK: false, Category: "probe_io", Detail: "probe spawn failed: " + err.Error() + " | " + output}
}
return netProbeVerdict(0, output)
}