Skip to content

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:

hexrift --yaml conglomerate.yaml <command> [options]

Global options

Option Default Description
--yaml PATH conglomerate.yaml Path to topology YAML
-V, --version Show version and exit

validate

hexrift validate

Validate the topology YAML against the Pydantic schema and report any errors.

Output on success:

Valid  conglomerate.yaml
  2 groups, 5 users, 2 exit regions, 1 hub regions, 6 nodes

Output on failure: prints the validation error and aborts.


show

hexrift 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

hexrift derive <entity>

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:

hexrift derive users
hexrift derive all

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 (nlA20nl)
--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

hexrift share <username> [options]

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

hexrift gen-keys [NODE_ID | --all] [options]

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

hexrift build [NODE_ID | --all] --xray|--haproxy [options]

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

hexrift gen-portal <PORTAL_ID> [options]
hexrift gen-portal --all [options]

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

hexrift diff <NODE_ID> [options]

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.