HydraIssues

hydraguard {venue,air,neckair} add: 'X added: name (addr)' preamble corrupts pipeable config output
done bug Project: Reporter: 11 May 2026 18:15

Description

Symptom

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.

Repro

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.

Why this matters

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.

Suggested fix

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.

Verification

  1. hydraguard air add tmp-test > /tmp/tt.conf produces a file whose first line is [Interface].
  2. The pre-existing 'Hydra Air added: ...' message still prints to the operator's terminal (because it's now on stderr, not redirected).
  3. wg-quick strip /tmp/tt.conf exits 0 and emits the trimmed config.
  4. Cleanup: hydraguard air remove tmp-test.

Related

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.

Custom Fields

affected_repo
hydraguard
affected_subcommands
venue add, air add, neckair add
discovered_via
hydraneck air-peer enrollment 2026-05-11
related_issue
142 (Citymesh venue rollout)
workaround
awk '/^\[Interface\]/{f=1} f{print}' before wg-quick

Comments (1)

api 11 May 2026 18:22

Fixed in hydraguard v1.10.10 (commit 3388298).

Moved the "X added: ..." status line and the "# === WireGuard Config — copy to device ===" banner to stderr (via cmd.ErrOrStderr()), and the generated config to stdout (via cmd.OutOrStdout()), across all three add subcommands — venue add, air add, neckair add. Operator still sees the narrative in an interactive run; > file now captures only the WG config.

Locked in by TestAddCommandsSeparateConfigFromNarrative covering all three subcommands. Test asserts:

  • first non-comment line of stdout is [Interface]
  • narrative status line is absent from stdout
  • narrative status line is present on stderr

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, was Hydra 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.