Conversation
|
Do we want a separate cli for tunnels or do we want to bake in functionality into datumctl? |
ea2df66 to
80ffdf7
Compare
|
Yeah, it's why this is a draft. I needed the functionality and didn't want to commit one way or the other yet. I explored doing it in datumctl and it would involve either replicating the Iroh sidecar in go or making the project hybrid with a rust component. This method uses all the same machinery as the GUI which felt like a better first pass. |
- Add 'tunnel' subcommand to datum-connect CLI with: - 'tunnel list': read-only listing of tunnels (no side effects) - 'tunnel listen': create/update and run tunnel in foreground - 'tunnel update': update tunnel label/endpoint - 'tunnel delete': delete a tunnel - Add 'nix run .#connect' app to flake.nix - Split find_connector_readonly for list operations - Remove side effects from tunnel list (no patching Connector) - Listen command: - Generates random label if not provided - Confirms before updating existing tunnel - Handles Ctrl+C to disable tunnel on exit
- Add 'auth' subcommand to CLI with: - 'auth status': Show current authentication and selected context - 'auth login': Log in via browser OAuth with account picker - 'auth logout': Log out and clear credentials - 'auth list': Show current authenticated user - 'auth switch': Log out current user and prompt for new login Also add is_authenticated(), login(), logout() methods to DatumCloudClient.
80ffdf7 to
01c3ab8
Compare
|
Ya the challenge is the core stuff we need is in rust so we'll need some magic to make the UX good |
|
How does this interact with the GUI based application? Would auth be shared? Since the GUI is locked to a specific project (because connectors are project-scoped resources), switching the authenticated user could break existing tunnels without the user knowing and it doesn't seem like we warn the user. |
|
It's all shared. I'll show what it looks like when Rust is done compiling... |
delete_project returned early when find_connector returned None, skipping deletion of HTTPProxy/ConnectorAdvertisement/TrafficProtectionPolicy. Connector lookup is only needed for post-deletion cleanup (deciding whether to delete the shared connector). Move it into an Option and gate the cleanup block on Some, so resource deletion always proceeds. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Three interrelated bugs fixed in the tunnel listen command: - Random label was generated before checking for an existing tunnel, so re-running listen on the same endpoint always triggered the update prompt. Moved label generation into the create-new path only; existing tunnels reuse their stored label unless --label is explicitly provided and differs. - Default label format changed from tunnel-<u16> (collides with resource ID format) to 12 hex chars of random entropy (e.g. a3f9c2e1b047). Adds hex as a dependency. - tunnel listen was missing the HeartbeatAgent that continuously patches status.connectionDetails on the connector (relay URL, addresses, public key). Without it the gateway has no routing info and tunnels never carry traffic. Now starts the heartbeat and registers the project before enabling the tunnel, then polls until accepted+programmed before printing the hostname. Also simplifies tunnel delete output: connector cleanup is an internal detail, so "Deleted tunnel <id>" replaces "(connector deleted: false)". Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- After auth login/switch, prompt user to select an org and project
and persist the selection as the active context
- Store the selected context in config.yml instead of a separate file
- Add --project flag to the tunnel command to override the active
project for a single invocation
- Add projects list and projects switch commands for managing the
active project outside of the auth flow
- Fix tunnel listen to print id and label after creation
|
Here's a short demo of where I've gotten with this: headless-tunnels-demo.mp4 |
|
@drewr Nice demo. |
|
@drewr this is slick. lm planning on splitting off the app repo from the gateway repo and we should consider where we'd want this cli to live. last piece there would be a small enhancement around how we could inject this rust binary into datumctl (if we want to) |
|
Great feedback @richardhenwood, thanks! |
|
@zachsmith1 wrote:
I think if we factor out the local process to a standalone rust utility like you're proposing it makes more sense for this to live in datumctl. I originally went that direction but didn't want to either rewrite the iroh integration in go or repackage this in an awkward way. |
|
I've had some instability with this and had both gpt-5.4 and sonnet-4.6 chewing on it:
Fix incoming. |
|
There is a lot to unwrap in my opinion here, a lot around product so I am not sure I have enough context to help here. Something is an old discussion we had here datum-cloud/enhancements#582 if you look for the
In practice what I was trying to highlight here is the mood kubernetes and other cli tool develop when you do that everything you run starts from a unique source of truth (for kubernetes it is the It will feel a lot easier to push for a plugin ecosystem like the one kubectl and others developed where binaries starting with But if we can not agree on some common practices, like authentication the outcome for a user will be pretty poor, in this case I feel like we should just "give up" and release different binaries working their own way. I am not saying that we should have in place the ability to switch and persist in between accounts/instances because I know we do not know yet datum-cloud/enhancements#653 (comment) but maybe since we do not know we can just take what we have today as common denominator until we figure out what's next. So the way I envision the evolution of this PR is a binary that serves only the business logic to manage tunnels and connections and demands authentication to the same login used by the datumctl (or the datumctl changes to turn to the same used here and from desktop) This is what I am trying to push to but as I said product wise I am not sure I have enough context to push into a direction vs another. |
The gateway sends `CONNECT localhost:<port>` regardless of whether the tunnel was registered with `localhost` or `127.0.0.1`, causing auth to fail with Forbidden and the caller to see "upstream connect error or disconnect/reset before headers." Normalize `localhost`, `127.0.0.1`, and `::1` to a canonical form on both sides of the host comparison in `tcp_proxy_exists`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Gateway hostname normalization — needs investigationWhile debugging an "upstream connect error or disconnect/reset before headers" report, I found the root cause in commit e2d868d: the gateway sends The client-side fix normalizes Observable behavior (from The Questions for the gateway team:
The client-side fix is defensive and correct either way, but if the gateway is doing unintended normalization, fixing it there would be cleaner and might surface other subtle issues. |
|
Follow-up on the gateway hostname question: the gateway sending The |
Gateway iroh connection establishment delay (~10–18 min)Confirmed a second, more impactful issue distinct from the localhost normalization fix. Observed behavior: After Evidence from logs:
Once the gateway has the iroh path up, traffic flows correctly (confirmed working). Root cause: The gateway appears to poll or process ConnectorAdvertisements with a long interval, delaying proactive iroh connection establishment. The client is reachable immediately via relay, but the gateway doesn't attempt to connect until it processes the advertisement. Suggested fix (gateway-side): When a new or updated ConnectorAdvertisement is observed, proactively establish the iroh connection to the endpoint rather than waiting for the next poll cycle. This would bring the ready-to-serve time in line with the "Tunnel ready" message the user already sees. |
|
@zachsmith1 Something seems to have changed between Envoy and iroh-gateway. The original code in this PR worked fine. Agents haven't been able to find anything else in the client code to fix. Any ideas as to what it is? @Frando @b5 Does the agent's diagnosis in the last comment track with reality, and if so, any upstream changes you feel like would cause it?
|
|
@drewr im not sure the agents comments are correct from an envoy/iroh-gateway perspective. I don't think we've touched anything on that path in some time. I'm seeing a new proxy take about 90 seconds to start up though. This is that karmada bug most likely or maybe when we enabled TPP it added some overhead. My tunnel works immediately though once its up. Taking a step back, is the problem you're seeing that it takes 20 minutes for any tunnel traffic to work for you? |
|
@drewr the ConnectorAdvertisement isn't the authority in the httpproxy/tunnel. Its actually based on the endpoint configured in the HttpProxy. That will have the host/port that envoy CONNECTs to iroh-gateway with. The desktop app today keeps both of those in sync |
Yes, after running I'm having an agent compare the client implementations. Stay tuned. |
Note on tunnel warm-up delayThe CLI and UI share the same repo path (
The CLI has never had |
|
@drewr we don't use the n0des api anymore. thats from the non-datum infra world |

Summary
This PR ships the CLI client for Datum Connect tunneling — the headless equivalent of the desktop UI. It lets users authenticate, manage projects, and expose local services to public hostnames without launching the GUI.
Building
Rust tooling only (no Nix required):
Or with Nix:
nix run .#cli -- --helpCommands
auth
projects
tunnel
tunnel listenruns in the foreground. It creates or reuses a tunnel for the given endpoint, starts the heartbeat agent so the gateway has routing info, enables the tunnel, and polls until it is accepted and programmed before printing the public hostname.Ctrl+Cdisables the tunnel and exits.The
--projectflag overrides the active project for a single invocation without changing the stored selection.Project selection
The active project is stored in
config.yml(default:~/.local/share/Datum/config.yml, overridable via$DATUM_CONNECT_REPO). It is set interactively afterauth loginorauth switch, or explicitly withprojects switch.Example session
Bug fixes (found during testing)
HeartbeatAgentthat continuously patchesstatus.connectionDetailson the connector. Without it the gateway has no routing info. Fixed:tunnel listennow starts the heartbeat and registers the project before enabling the tunnel.tunnel listenon an existing endpoint always prompted for update: Random label was generated before checking for an existing tunnel, so it always differed. Fixed: label generation moved into the create-new path; existing tunnels reuse their stored label unless--labelis explicitly given.delete_projectreturned early if no connector was found, skipping deletion of HTTPProxy/ConnectorAdvertisement/TrafficProtectionPolicy. Fixed: connector lookup is only needed for post-deletion cleanup and no longer gates resource deletion.tunnel-<u16>format: Collided visually with resource ID format. Switched to 12 hex chars of random entropy (e.g.a3f9c2e1b047).Test plan
cargo run -p datum-connect -- auth logincompletes OAuth and prompts for project selectionprojects listshows all orgs/projects with active one markedprojects switchpersists new selection toconfig.ymltunnel listen --endpoint 127.0.0.1:<port>creates tunnel, prints hostname, disables on Ctrl+Ctunnel listenon the same endpoint reuses the existing tunnel without promptingtunnel listen --project <id>uses the specified projecttunnel listshows tunnels in the active projecttunnel deleteremoves a tunnel cleanly