siderolink-doctor is a standalone support diagnostic for self-hosted Omni.
It tests the complete SideroLink path without requiring access to the Talos
machine that failed to join.
The tool consumes the same machine join YAML given to Talos, provisions a temporary Link with an ephemeral identity, and runs WireGuard entirely in userspace. It needs no root privileges and does not create a kernel network interface.
In the default auto mode the tool:
- parses and validates the multi-document Talos join configuration;
- provisions a temporary SideroLink peer through the Omni Machine API;
- honors
grpc_tunnel=trueor server-forced tunnel mode when present; - otherwise tests direct WireGuard over every advertised UDP endpoint;
- verifies a real WireGuard handshake;
- opens TCP through the tunnel to Omni's event sink;
- publishes a visible, clearly labeled synthetic machine-status event;
- probes the kernel-log TCP endpoint when present;
- if direct mode fails, re-provisions the same temporary Link for WireGuard-over-gRPC and repeats the required probes.
Direct and gRPC evidence is kept separate. A direct handshake failure followed by a successful gRPC run is strong evidence that a firewall, load balancer, or NAT path is blocking, misrouting, or modifying UDP. A successful handshake followed by a failed event-sink probe is reported as an in-tunnel service issue, not as a UDP failure.
Talos does not automatically fall back from direct UDP to gRPC. The doctor performs this second attempt specifically for diagnosis.
Go 1.26.5 or newer is required.
make unit-tests-race
make siderolink-doctorKres generates the Makefile, Dockerfile, CI workflows, lint configuration, and
release metadata from .kres.yaml.
The Linux binaries are written to _out/
for amd64 and arm64.
Regenerate project automation after changing .kres.yaml:
make rekressiderolink-doctor check \
--join-config machine-join.yaml \
--output text \
--timeout 90sRead from standard input:
cat machine-join.yaml | siderolink-doctor check --join-config -Run the published container on Linux using the host network so Docker does not add another network path to the diagnostic:
docker run --rm -i --network host \
ghcr.io/siderolabs/siderolink-doctor:latest \
check --join-config - < machine-join.yamlMachine-readable evidence:
siderolink-doctor check --join-config machine-join.yaml --output jsonAdvanced transport isolation:
# Direct only; disables automatic gRPC fallback.
siderolink-doctor check --join-config machine-join.yaml --force-mode direct
# Request gRPC tunnel mode immediately.
siderolink-doctor check --join-config machine-join.yaml --force-mode grpc| Code | Meaning |
|---|---|
0 |
The configured/selected transport passed all required probes. |
2 |
Direct mode failed and automatic gRPC fallback succeeded. |
3 |
No tested data-plane transport passed all required probes. |
4 |
Machine API provisioning or authorization failed. |
5 |
The join config or CLI input was invalid. |
1 |
The tool itself failed. |
Exit 2 is an actionable diagnostic result rather than a tool malfunction.
The join YAML contains a secret join token. The tool:
- never prints or logs the raw YAML;
- redacts the
jointokenquery value; - never reports the ephemeral WireGuard private key or node-unique token;
- applies a final token-leak check before writing a report;
- uses bounded per-probe and overall timeouts.
The synthetic event is deliberately visible on the temporary machine and is
labeled SideroLinkDoctorSyntheticEvent with actor siderolink-doctor.
Successful provisioning intentionally leaves the temporary Link in Omni so support can inspect server-side evidence. After the investigation, delete the Link in the Omni UI or run the command printed in the report:
omnictl delete links.omni.sidero.dev <temporary-link-uuid>The tool does not authenticate to Omni's management API and therefore does not delete the Link automatically.