diode is the Go CLI for connecting a device or service to the Diode mesh network. It can:
- publish local TCP/TLS/UDP ports to remote Diode clients
- run a local SOCKS5 proxy for reaching
.diodeservices - expose files over the network and transfer them with
push/pull - resolve BNS names and query network/device information
- run contract-driven perimeter configuration with
join - act as an MCP server for AI tooling over stdio
All traffic is routed through the Diode network and secured with Diode's client identity and end-to-end encrypted sessions.
- how to build and run the client
- what gets created on first run
- the commands you are most likely to use
- how to publish ports, browse Diode services, transfer files, use SSH, and run MCP
- where config lives, how the config API works, and the common pitfalls
- Go
1.22.xor newer enough to build the module as checked in - CGO enabled for normal builds
- OpenSSL build support available locally
Optional:
- WireGuard installed if you plan to use
diode join -wireguard - GTK/AppIndicator libraries if you want tray support on Linux
- OpenSSH client tools if you plan to use
diode ssh
If you want the packaged CLI instead of building from source, the published CLI docs point to:
- Downloads: https://diode.io/download
- Install script:
curl -Ssf https://diode.io/install.sh | sh
go mod download
make openssl
makeThis builds the main binary as ./diode.
If you only want to run it during development:
go run ./cmd/diode --helpThe client stores local state in a small file database. By default that database is created at:
- Linux:
~/.config/diode/private.db - macOS:
~/Library/Application Support/diode/private.db - Windows:
C:\Users\<yourname>\AppData\Roaming\diode\private.db
On first start the client automatically creates a secp256k1 private key and derives your Diode client address from it. You can inspect what is stored locally with:
./diode -update=false config -listImportant stored values:
private: your client private keyfleet: the fleet/perimeter address this client uses
If fleet has not been configured yet, the client uses the default development fleet until you either:
- run
diode resetto create a new fleet contract for this client, or - set an existing fleet manually with
diode config -set fleet=0x...
./diode -update=false config -listIf your app listens on localhost:8080 and you want it exposed on public Diode port 80:
./diode publish -public 8080:80If the publish succeeds, the client logs the public gateway URL:
http://<name-or-address>.diode.link/for public port80https://<name-or-address>.diode.link:<port>/for some public high ports such as8000-8100
./diode socksdThis starts a SOCKS5 proxy on 127.0.0.1:1080. Point your browser or app at that proxy to reach .diode names directly.
./diode fetch -url http://mydevice.diode
./diode fetch -url https://mydevice.diode.link:8080/file.txt -output file.txtTop-level commands:
publish: publish local services to the networksocksd: run a local SOCKS5 proxyfetch: issue HTTP requests to Diode endpointsfiles: run a published HTTP file listenerpush/pull: upload or download files from a Diode file listenerssh: connect to a Diode target through an auto-managed local SOCKS proxyscp: copy files to/from a Diode target through an auto-managed local SOCKS proxyjoin: apply on-chain perimeter properties and optional WireGuard configbns: register, transfer, and resolve BNS namesquery: resolve a Diode address or name and print device ticketstoken: check balance or transfer DIODEconfig: inspect or change local stored valuesmcp: run the client as an MCP server over stdin/stdoutgateway: run a public HTTP/HTTPS gatewaytime: query consensus timeversion: print build version
One implementation detail that is easy to miss: if you run diode with no subcommand, the CLI falls back to publish. Use explicit subcommands in scripts and docs.
Another important parser rule: global options must come before the subcommand.
./diode -debug=true publish -public 80:80Not:
./diode publish -debug=true -public 80:80publish is the core command for exposing a local service.
./diode publish -public 8080:80
./diode publish -protected 3000:3000
./diode publish -private 22:22,0xabc...,myfriendPort mapping forms:
<local_port>: publish the same local and remote port<local_port>:<remote_port>: publish local to a different external port<host>:<local_port>:<remote_port>: bind to a specific local host- append
:tcp,:tls, or:udpto force a protocol
Examples:
./diode publish -public 8080
./diode publish -public 127.0.0.1:8080:80:tcp
./diode publish -public 80:80,2368:8025
./diode publish -private 22:22:tcp,0x1111111111111111111111111111111111111111
./diode publish -protected 3000:3000Publish modes:
-public: anybody can connect-private: you must provide at least one allowlisted address or BNS name-protected: intended for fleet/perimeter-controlled access
Useful flags:
-socksd: start a SOCKS proxy alongside the publisher-proxy_host/-proxy_port: configure that proxy-sshd: publish the embedded Diode SSH service-files: add one or more file listeners to the same long-running publisher-fileroot: root path for all-fileslisteners on that command
The simplest static-site workflow from the public docs is:
mkdir project
cd project
echo "Hello World" > index.html
../diode publish -httpThat starts the built-in static server and publishes it through Diode.
If you run publish without any ports, files listeners, or binds, the client exits with an error.
The client includes a simple HTTP file listener and matching upload/download commands.
Start a public file listener on Diode port 8080:
./diode files 8080Serve files relative to a specific directory:
./diode files -fileroot /var/inbox 8080Private file listener:
./diode files -fileroot /var/inbox 8080,mydevice,0x0000000000000000000000000000000000000001Upload a file:
./diode push ./photo.jpg mydevice.diode.link:8080
./diode push ./photo.jpg mydevice.diode.link:8080:photos/vacation.jpgDownload a file:
./diode pull mydevice.diode.link:8080:photos/vacation.jpg
./diode pull mydevice.diode.link:8080:photos/vacation.jpg ./vacation.jpgImportant behavior:
- if
-filerootis omitted, the listener serves relative to the process working directory at startup diode filesflags must come before the positional<files-spec>because Go'sflagparser stops at the first non-flag argumentpushwith only<peer>:<port>writes to the basename of the local file on the remote sidepullwithout a local destination writes to./<basename(remote-path)>
The full contract for file serving and transfer is documented in docs/file-transfer-spec.md.
Start a local SOCKS proxy:
./diode socksdDefault listener:
- host:
127.0.0.1 - port:
1080
Custom port:
./diode socksd -socksd_host 127.0.0.1 -socksd_port 8082The repo includes a proxy.pac file you can use for browser proxy configuration. Typical usage is to send .diode, .diode.link, and .diode.ws traffic through the SOCKS proxy.
Other common SOCKS-based workflows from the CLI docs:
curl -x socks5h://localhost:1080 mydevice.diode
ssh -o "ProxyCommand=nc -X 5 -x localhost:1080 %h %p" user@mydevice.diode
nc -X 5 -x localhost:1080 mydevice.diode.link 8080That last pattern is useful for tools like VLC that do not fully support SOCKS hostname resolution on their own.
You can also use a bind rule to open a local port that forwards to a remote Diode service:
./diode -bind auto:mydevice.diode:80:tls socksdauto lets the OS choose a local port. The client prints the resolved local port after startup.
This same -bind feature is what the public docs use for remote SMB / drive mapping style workflows. Example:
./diode -bind 1039:0xREMOTEDEVICEADDRESS:445 socksdAfter that, a local app can connect to localhost:1039 as if it were the remote service.
diode ssh is a convenience wrapper around OpenSSH. It:
- starts the Diode client
- spins up a temporary local SOCKS proxy
- generates a temporary SSH identity
- launches your local
sshbinary with the right proxy command
Example:
./diode ssh ubuntu@mymachine.diode -p 22Notes:
- the command is marked beta in the client
- do not put the port in the hostname; use
-p 22, notubuntu@mymachine.diode:22 - OpenSSH tools such as
sshandssh-keygenmust be installed locally
diode scp is the file-copy counterpart to diode ssh. It wraps the local
scp binary using the same auto-managed Diode SOCKS proxy, ephemeral SSH
identity, and ProxyCommand wiring, so remote paths that target a .diode
host (or raw Diode address) get tunnelled through the Diode network.
Examples:
./diode scp ./photo.jpg ubuntu@mymachine.diode:/tmp/photo.jpg
./diode scp -P 22 ubuntu@mymachine.diode:/etc/hostname ./hostname
./diode scp -r ./dir ubuntu@mymachine.diode:/tmp/dirNotes:
- same BETA status as
diode ssh; flags may still change - just like
diode ssh, do not put a port in the hostname; use-P PORT(uppercase, as scp expects it) - OpenSSH tools (
scp,ssh,ssh-keygen) must be installed locally - arguments after
scpare passed through to the system scp, so standard flags such as-r,-p,-C,-o,-P, etc. all work
If you want to expose a normal local SSH daemon over Diode:
./diode publish -public 22:22
./diode publish -private 22:22,0x1111111111111111111111111111111111111111
./diode publish -protected 22:22If you want to publish the embedded Diode SSH service instead:
./diode publish -sshd protected:2222:ubuntu
./diode publish -sshd private:2222:ubuntu,0x1111111111111111111111111111111111111111fetch is a Diode-aware HTTP client. It only accepts Diode URLs, not arbitrary web2 URLs.
Examples:
./diode fetch -url http://myservice.diode
./diode fetch -url https://myservice.diode.link:8080/api -method POST -data '{"ok":true}' -header 'content-type: application/json'
./diode fetch -url diode://myservice.diode/pathSupported methods:
GETPOSTPUTDELETEOPTION
PATCH is currently not enabled.
BNS is the blockchain name service used by Diode names.
Lookup:
./diode bns -lookup mydevice
./diode bns -account mydeviceRegister:
./diode bns -register mydevice=0x1234...If you register BNS names with the CLI, the ownership workflow is tied to this client's local wallet. Back up the local private.db if you rely on CLI-managed BNS names. If you want one wallet to manage names independently of any single CLI install, the public docs recommend using the Diode Network Explorer with MetaMask instead.
Transfer:
./diode bns -transfer mydevice=0xabcd...Unregister:
./diode bns -unregister mydeviceBNS names are lowercase and must be between 7 and 32 characters using a-z, 0-9, and -.
Resolve an address or name to device tickets:
./diode query -address 0x1234...
./diode query -address mydeviceCheck your balance:
./diode token -balanceSend DIODE:
./diode token -to 0x1234... -value 1millidiode -gasprice 10gwei
./diode token -to mydevice -value 1diode -gasprice 10gweijoin is the command for contract-driven perimeter management. It watches an on-chain property set and applies the resulting local behavior.
Typical use:
./diode join 0xB7A5bd0345EF1Cc5E66bf61BdeC17D2461fBd968Dry-run a single sync:
./diode join -dry 0xB7A5bd0345EF1Cc5E66bf61BdeC17D2461fBd968Choose network:
./diode join -network testnet 0xB7A5bd0345EF1Cc5E66bf61BdeC17D2461fBd968Generate and print a WireGuard public key without fully joining:
./diode join -wireguard
./diode join -wireguard -suffix stagingWhat join can consume from the perimeter/device properties includes:
- published
public,private, andprotectedport definitions sshdwireguardsocksdbinddebugdiodeaddrsfleetextra_config
WireGuard notes:
- the on-chain
wireguardproperty must not containPrivateKey - the client generates and stores the private key locally
- generated config paths are OS-specific
- bringing interfaces up may require administrator privileges
- manual edits to generated WireGuard configs are overwritten on the next sync
The current README keeps the essentials here. The implementation details live in the command and are documented inline in the code around cmd/diode/join.go.
The config command works on the local key-value store in the Diode database.
List values:
./diode config -listShow private material too:
./diode config -list -unsafeSet a value:
./diode config -set fleet=0x1234...Delete a value:
./diode config -delete fleetCommon keys:
fleetprivate
Be careful with -unsafe: it prints private key material.
Most users should not start with reset. Use it when you intentionally want this client to create and own a new fleet contract.
./diode resetWhat it does:
- deploys a new fleet contract
- allowlists the current device in that fleet
- stores the new fleet locally for future commands
Why this is not in quick start:
- it changes persistent client state
- it is a blockchain write operation, not a read-only setup check
- many users will connect to an existing fleet or only need client-side tools such as
socksd,fetch, or public publishing
If you already have a fleet address and just want to use it, set it directly instead:
./diode config -set fleet=0x1234...The client only loads a YAML config file when you explicitly pass -configpath.
Example:
cp .diode.yml.example .diode.yml
./diode -configpath ./.diode.yml socksdThat same flag is also required if you want the config API to save changes back to a YAML file.
Without -configpath, the client reads and writes its normal file database instead.
The example config lives at .diode.yml.example.
Daemon-style commands such as publish, socksd, gateway, files, and join can expose the config API:
./diode -api=true -apiaddr=localhost:1081 publish -public 8080:80Endpoints:
GET /for a basic health checkGET /configfor the current client config summaryPUT /configto update config-file-backed settingsGET /connection-client-id?peer=<remoteAddr>to map a published inbound TCP peer to a verified Diode client ID
Important gotcha:
- the current implementation requires
Content-Type: application/jsonon requests handled by this API, includingGET /config
Example:
curl -H 'Content-Type: application/json' http://localhost:1081/configThe connection-client-id endpoint is used by the example app in examples/client_id/README.md.
diode mcp runs the client as a Model Context Protocol server over stdio.
Examples:
./diode mcp
./diode mcp -mcp-preset=minimal
./diode mcp -mcp-tool=diode_deployPresets:
minimalchainfilesdeployall
Available tool families include:
- version and client identity
- address queries
- file push/pull
- deploy upload support
Full MCP behavior and tool schemas are documented in docs/mcp-spec.md.
gateway runs an HTTP or HTTPS gateway similar to diode.link.
Example:
./diode gateway -httpd_port 8080 -httpsd_port 8443 -secure -certpath ./fullchain.pem -privpath ./privkey.pemUseful options:
-socksd: also start the local SOCKS proxy-allow_redirect: redirect HTTP to HTTPS-edge_acme: let the gateway manage certificates automatically-additional_ports: extra TLS ports
This mode is mainly for operators who want to run their own public-facing Diode gateway.
Common global flags you will actually use:
-configpath <file>: load YAML config from a file-dbpath <file>: change the database path-diodeaddrs <addr>: override the bootstrap RPC peers-bind <local>:<remote>:<port>:<proto>: create a local forward through Diode-maxports <n>: cap concurrent ports per device-tray=true: show the tray icon on supported platforms-update=false: disable auto-update on startup-debug=true: enable debug logging-api=true -apiaddr=host:port: enable the config API-pprofport <port>: enablenet/http/pprofon localhost
To see every flag and subcommand:
./diode --help
./diode publish --helpTray support is integrated into the main diode binary.
- enable with
-tray=true - supported on Windows, macOS, and most Linux desktop environments
- automatically disabled under WSL
On Linux you may need runtime packages such as:
libgtk-3-0libayatana-appindicator3-1
For older Linux environments that need legacy AppIndicator support:
make diode_tray_legacyRun tests:
make testRun CI-like checks locally:
make ci_testFormat:
make formatLint:
make lintSecurity checks:
make seccheckUseful artifacts and examples:
- Gauge load tool: cmd/gauge/README.md
- Client ID example: examples/client_id/README.md
- verify outbound access to the configured Diode RPC addresses
- try
./diode -update=false time - override peers with
-diodeaddrsif needed
- use
diode config -listto find your address - if reverse BNS is configured, the client logs
Client name - public port
80maps tohttp://<name-or-address>.diode.link/ - public ports in the secure gateway ranges log
https://<name-or-address>.diode.link:<port>/ - otherwise connect through another Diode client using
.diodeaddressing and a SOCKS proxy
Put -fileroot before the <files-spec> argument:
./diode files -fileroot /srv/data 8080Not:
./diode files 8080 -fileroot /srv/dataThat is expected. diode fetch only accepts Diode URLs such as:
http://name.diodehttps://name.diode.link:8080diode://name.diode/path
Send Content-Type: application/json, even on GET /config.
Run the command with the privileges required to write to the WireGuard config directory or bring the interface up manually after the file is generated.
This project is licensed under the Diode License, Version 1.1. See LICENSE.
