CLI Reference¶
All commands require a topology YAML file passed. By default the path is conglomerate.yaml.
It is possible to override that via the global --yaml option:
Global options¶
| Option | Default | Description |
|---|---|---|
--yaml PATH |
conglomerate.yaml |
Path to topology YAML |
-V, --version |
— | Show version and exit |
validate¶
Validate the topology YAML against the Pydantic schema and report any errors.
Output on success:
Output on failure: prints the validation error and aborts.
show¶
Visualize the full network topology as a tree: global settings, regions, nodes, users (by group), portals, and guests. Each portal lists its members, its strict state, and one line per published port carrying that port's source allowlist and node scope.
derive¶
Show derived identifiers in a table.
Arguments:
| Argument | Description |
|---|---|
users |
UUIDs, emails, server UUIDs, and guest shortIds for every user |
groups |
ShortIds for every group |
portals |
Tags, UUIDs, emails, short IDs, member users, strict state, and published ports (port/network -> target allow: … nodes: …, where allow: any means open to the internet and nodes: all means every hub node binds it) for every portal |
nodes |
ShortIds / hub-exit UUIDs for every node |
all |
All of the above |
Example:
nodes¶
hexrift nodes list [--names | --domains | --json] [--type exit|hub]
hexrift nodes add <NODE_ID> [options]
hexrift nodes remove <NODE_ID>
add and remove edit the regions: section of the topology YAML in place: only the edited lines change, everything else is kept byte for byte, and YAML shapes that cannot be spliced safely are refused with a message saying why. After writing, the file is re-validated. A validation failure is reported as a warning and the edit is kept, so a missing piece such as an exit node's reality block can be filled in by hand.
list¶
List nodes with their hostnames. Designed for use in shell scripts.
Options:
| Option | Description |
|---|---|
--names |
Output node IDs only (one per line) |
--domains |
Output hostnames only (one per line) |
--json |
Output a JSON array of {id, hostname, region, type} objects, in topology order |
--type exit\|hub |
Filter by region type |
Examples:
# Tab-separated ID + hostname (default)
hexrift nodes list
# All exit node IDs — useful for loops
hexrift nodes list --names --type exit
# All hub hostnames
hexrift nodes list --domains --type hub
# Structured output for other tools
hexrift nodes list --json --type exit | jq -r '.[].hostname'
--json output:
[
{"id": "nlA00", "hostname": "nlA00.aphelion.example.com", "region": "nl", "type": "exit"},
{"id": "mskA00", "hostname": "mskA00.perigee.example.com", "region": "msk", "type": "hub"}
]
add¶
Add NODE_ID to its region, creating the region when missing (exits get an unused vless_route). A balanced region without lb_fallback gets its first primary node written as one. Adding a node that is already present is a no-op.
Options:
| Option | Default | Description |
|---|---|---|
--type exit\|hub |
region's type | Region type, required only when the region has to be created |
--region ID |
leading lowercase letters of NODE_ID |
Region to add the node to (nlA20 → nl) |
--hostname HOST |
derived | Exits: <NODE_ID>.<aphelion_domain>. Hubs: domain of the existing hub nodes |
--no-ipv6 |
off | Write ipv6: false on the node |
--hysteria |
on when the region has protocol: hysteria or a hysteria: block |
Write a hysteria block (obfs: true, sni = hostname, masquerade_url from the Reality dest) |
--reality-dest HOST:PORT |
— | Reality dest, also the Hysteria masquerade target |
--reality-server-names LIST |
— | Comma-separated Reality server_names, requires --reality-dest |
--reality-xhttp-path PATH |
— | Reality xhttp_path, required together with --reality-dest |
remove¶
Remove NODE_ID from its region, dropping hub_routes and lb_fallback entries that point at it, and routes to the region once it is empty. An emptied region keeps its settings. A node that is not in the topology is skipped.
Examples:
# New exit node in the existing `nl` region
hexrift nodes add nlA40 --reality-dest www.samsung.com:443 \
--reality-server-names www.samsung.com,samsung.com --reality-xhttp-path /login/
# New hub node, hostname follows the other `msk` hubs
hexrift nodes add mskA30 --no-ipv6
# First node of a new exit region, `--type` is required
hexrift nodes add frA00 --type exit
# Decommission
hexrift nodes remove nlA40
share¶
Generate VLESS or Hysteria share URLs — or WireGuard client configs — for a user. Direct URLs are Reality by default; a hub that serves tls gets one security=tls URL per node, labelled TLS.
Arguments:
| Argument | Description |
|---|---|
USERNAME |
Username defined in users[].username |
Options:
| Option | Default | Description |
|---|---|---|
--hub HUB_ID |
all hub nodes | Generate config for a specific hub node |
--fp FINGERPRINT |
from config | Client TLS fingerprint |
--cdn |
off | Generate CDN URL instead of the direct URL |
--hy2, --hysteria |
off | Generate a hysteria2:// URL instead of the direct URL |
--wg, --wireguard |
off | Generate a WireGuard client .conf instead of a VLESS URL |
--server |
off | Generate config for the user's server identity |
--guest LABEL |
— | Generate config for a specific guest identity |
--all-guests |
off | Generate config for all guests of the user |
--bare |
off | Output raw URLs/configs only — no formatting, suitable for piping |
--keys-dir PATH |
keys |
Directory containing key files |
Note
--guest and --all-guests are mutually exclusive. --server cannot be combined
with --guest or --all-guests. --wg, --cdn and --hy2 are mutually exclusive.
Direct URLs
A hub yields a URL only when the user is rendered on it: xhttp access for hubs
serving Reality, tls access for hubs serving tls. --server identities are on every
Reality hub, and on TLS hubs only with tls access.
WireGuard configs
--wg requires the user to have wireguard access and the hub to define
defaults.hub.wireguard. Generated configs use 1.1.1.1 as the client DNS resolver.
Hysteria URLs
--hy2 requires the user to have hysteria access and the hub to render a Hysteria
listener. With the default derived certificate the URL carries insecure=1&pinSHA256=…;
with an operator certificate it carries insecure=0. obfs=salamander&obfs-password=…
is added when the listener enables obfs.
Examples:
# Direct link for alice on all hubs
hexrift share alice
# CDN link on a specific hub
hexrift share alice --cdn --hub euH00
# Hysteria 2 link, bare, piped to clipboard
hexrift share alice --hy2 --bare | clip
# WireGuard client config for alice
hexrift share alice --wg
# WireGuard configs for all of alice's guests, piped to clipboard
hexrift share alice --wg --all-guests --bare | clip
# All guest links, piped to clipboard
hexrift share alice --all-guests --bare | clip
gen-keys¶
Generate x25519 Reality keypairs and ML-KEM 768 encryption keys. One YAML file is written per node to <keys-dir>/<nodeId>.yaml.
Info
Hub nodes in the same region automatically share a keypair.
Arguments:
| Argument | Description |
|---|---|
NODE_ID |
Generate keys for a single node |
Options:
| Option | Default | Description |
|---|---|---|
--all |
off | Generate keys for all nodes in the topology |
--force |
off | Overwrite existing key files |
--keys-dir PATH |
keys |
Directory to store key files |
--force invalidates everything issued for that node
A node's Reality keypair is baked into every share URL and client config generated for it. Overwriting it means every client of that node fails to connect until its config is regenerated and redistributed. Without --force existing key files are left alone, which is why it is not the default.
Examples:
# Single node
hexrift gen-keys nlA00
# All nodes, overwrite existing
hexrift gen-keys --all --force
# Custom keys directory
hexrift gen-keys --all --keys-dir /etc/hexrift/keys
build¶
Generate Xray config.json and/or HAProxy .cfg for node(s). Output is written to <out-dir>/<nodeId>/.
Generated configs are secrets
A node's config.json embeds its Reality private key and every client UUID, so it is written with 0o600 permissions. Keep that out of version control and off any shared artifact store — treat it like the key file it is derived from.
Arguments:
| Argument | Description |
|---|---|
NODE_ID |
Build config for a single node |
Options:
| Option | Default | Description |
|---|---|---|
--xray |
off | Render Xray config.json |
--haproxy |
off | Render HAProxy haproxy.cfg |
--all |
off | Build configs for all nodes |
--out-dir PATH |
configs |
Output directory |
--keys-dir PATH |
keys |
Directory containing key files |
Warning
At least one of --xray or --haproxy must be provided.
Examples:
# Xray config for one node
hexrift build nlA00 --xray
# All nodes, both Xray and HAProxy
hexrift build --all --xray --haproxy --out-dir ./out
# Custom directories
hexrift build euH00 --xray --keys-dir /etc/hexrift/keys --out-dir /etc/xray
gen-portal¶
Generate the Xray bridge config.json for portal(s) declared in the top-level portals: section. Each config is written to <out-dir>/<portal-id>/config.json with 0o600 permissions (it embeds key material). Deploy it on the portal machine; several machines may run the same config — the hubs pool their tunnels.
Unless the portal sets strict: false, the generated routing mirrors the portal's routes and publish matchers and blackholes everything else, so the config has to be regenerated whenever those matchers change.
Arguments:
| Argument | Description |
|---|---|
PORTAL_ID |
Portal to generate (or pass --all) |
Options:
| Option | Default | Description |
|---|---|---|
--all |
— | Generate configs for every portal |
--fp FINGERPRINT |
from config | Client TLS fingerprint |
--out-dir PATH |
configs/portals |
Output directory |
--keys-dir PATH |
keys |
Directory containing key files |
Examples:
# All portals
hexrift gen-portal --all
# A single portal, into a custom directory
hexrift gen-portal home --out-dir ./portals
Published ports
A portal's publish entries make hub nodes bind ports that reach into the portal-side network with no authentication and no user check — portals[].users does not apply to them. allow: any (as shown by derive portals) means no allowlist was configured, so the published port is exposed publicly and the target service's own authentication is the only line of defense; configure allow with restrictive trusted source IP ranges instead. Give a published service its own portal id rather than sharing one config across pooled machines.
diff¶
Show a unified diff between the freshly generated config.json and the currently deployed one.
Arguments:
| Argument | Description |
|---|---|
NODE_ID |
Node to diff |
Options:
| Option | Default | Description |
|---|---|---|
--current-dir PATH |
configs |
Directory containing currently deployed configs |
--keys-dir PATH |
keys |
Directory containing key files |
Examples:
# Diff against local configs/ directory
hexrift diff nlA00
# Diff against deployed config
hexrift diff nlA00 --current-dir /etc/xray
Prints No differences. if configs match.