hydraguard air add hydraneck > hydraneck.wg.conf (or venue add ..., or neckair add ...) produces a file whose first line is the success message — Hydra Air added: hydraneck (10.10.100.14/32) — followed by a blank line, then the actual WireGuard config block. wg-quick up wg0 chokes on the leading non-# text with Configuration parsing error and refuses to start the tunnel.
Discovered today (2026-05-11) while installing the freshly-added air-hydraneck peer on hydraneck.experiencenet.com. Workaround was awk '/^\[Interface\]/{f=1} f{print}' to strip the preamble in place; once cleaned, the tunnel started immediately and the handshake landed in ~3s.
ssh ubuntu@hydraguard.experiencenet.com 'sudo hydraguard air add test > /tmp/test.conf'
head -1 /tmp/test.conf # → 'Hydra Air added: test (10.10.100.X/32)' — NOT a comment, not part of any section
wg-quick strip /tmp/test.conf # parse error
Same structure observed on venue add cloud-seven and venue add rupelmonde today. Almost certainly identical for neckair add.
The runbooks (hydraguard/docs/runbooks/citymesh-venue.md and hydraneck/docs/runbooks/mesh-participation.md, both shipped earlier this session) document the workflow as:
hydraguard venue config <name> > /tmp/<name>.partner.conf
# hand off to partner
Any operator following the runbook and piping straight to wg-quick (or, worse, handing the file to a partner) hits the parse error. Today, the cloud-seven and rupelmonde .partner.conf files headed for Citymesh had to be hand-cleaned before they were safe to send.
Split stdout so the user-facing status message goes to stderr (or to stdout after the config block), so > file only captures the WG config:
// Wrong (what we have today):
fmt.Printf("Hydra Air added: %s (%s)\n\n", name, addr)
fmt.Print(generatedConf)
// Right:
fmt.Fprintf(os.Stderr, "Hydra Air added: %s (%s)\n", name, addr)
fmt.Print(generatedConf)
Call sites to fix (best guess from skim — verify against current code):
hydraguard/internal/cli/air.go (the Hydra Air added: ... line)hydraguard/internal/cli/venue.go (Venue added: ...)hydraguard/internal/cli/neckair.go (Neck Air added: ...)Alternative: keep the message on stdout but prefix it with # so wg-quick treats it as a comment. Slightly hackier but smaller diff and preserves the visible-when-not-piped-to-file behavior.
hydraguard air add tmp-test > /tmp/tt.conf produces a file whose first line is [Interface].wg-quick strip /tmp/tt.conf exits 0 and emits the trimmed config.hydraguard air remove tmp-test.Worked around manually for cloud-seven, rupelmonde, and hydraneck peer configs during today's #142 prep work. Once this bug is fixed, the runbooks should still work as written — no doc change needed.
Fixed in
hydraguard v1.10.10(commit3388298).Moved the
"X added: ..."status line and the"# === WireGuard Config — copy to device ==="banner to stderr (viacmd.ErrOrStderr()), and the generated config to stdout (viacmd.OutOrStdout()), across all three add subcommands —venue add,air add,neckair add. Operator still sees the narrative in an interactive run;> filenow captures only the WG config.Locked in by
TestAddCommandsSeparateConfigFromNarrativecovering all three subcommands. Test asserts:[Interface]Deployed to the Brussels hub (v1.10.10 now running, daemon PID 812220). Smoke-test on the live binary:
hydraguard air add smoke-test-149 > /tmp/smoke.conf 2> /tmp/smoke.err→ stdout starts with# Generated by HydraGuard...(wg-quick-safe comment, wasHydra Air added: ...before), stderr carries the narrative.Also a quiet win: the v1.10.9 → v1.10.10 update is the first one where the updater auto-restart actually worked end-to-end ("Restarting service hydraguard... Service hydraguard restarted.") — confirms #148 is genuinely closed.
The runbooks (citymesh-venue.md, mesh-participation.md) now work exactly as written — no doc change needed.