HexRift
Config generator for the Conglomerate distributed proxy network. Takes a topology definition and produces Xray JSON configs and HAProxy configs for every node. Hub nodes additionally support WireGuard and XDNS inbounds.
Installation
uv sync
Usage
All commands require a topology YAML file:
hexrift --yaml conglomerate.yaml <command>
Commands
| Command | Description |
|---|---|
validate |
Validate the topology YAML against the schema |
show |
Visualize the network topology (regions, nodes, users, guests, portals) |
derive [users|groups|portals|nodes|all] |
Show derived identifiers (UUIDs, shortIds, emails) |
nodes list [--names|--domains|--json] [--type exit|hub] |
List nodes with hostnames; machine-friendly output for automation |
nodes add NODE_ID [--type exit|hub] [--region ID] [--hostname HOST] [--no-ipv6] [--hysteria] [--reality-dest HOST:PORT] [--reality-server-names LIST] [--xhttp-path PATH] |
Add a node to its region in the topology YAML, creating the region when missing |
nodes remove NODE_ID |
Remove a node from the topology YAML, keeping its region |
gen-keys [NODE_ID|--all] [--force] [--keys-dir PATH] |
Generate x25519 + ML-KEM 768 keypairs for nodes |
build [NODE_ID|--all] --xray|--haproxy [--keys-dir PATH] [--out-dir PATH] |
Build Xray config.json and/or HAProxy .cfg |
gen-portal [PORTAL_ID|--all] [--group ID] [--fp FINGERPRINT] [--out-dir PATH] [--keys-dir PATH] |
Build Xray bridge config.json for portal(s) from the top-level portals: section |
diff NODE_ID [--current-dir PATH] [--keys-dir PATH] |
Diff generated config against deployed config |
share USERNAME [--hub NODE_ID] [--fp FINGERPRINT] [--cdn] [--wg] [--server] [--guest LABEL] [--all-guests] [--bare] [--keys-dir PATH] |
Generate VLESS share URLs or WireGuard client configs (--wg) |
Examples
# Validate topology
hexrift validate
# Visualize topology
hexrift show
# Show all derived identifiers
hexrift derive all
# List all exit node IDs (for scripts)
hexrift nodes list --names --type exit
# Structured node list (id, hostname, region, type) for other tools
hexrift nodes list --json
# Add a node to the topology file (creates the region if needed)
hexrift nodes add nlA20 --reality-dest www.samsung.com:443 --xhttp-path /login/
# Generate keys for all nodes
hexrift gen-keys --all
# Build Xray config for a specific node
hexrift build nlA00 --xray --out-dir ./out
# Build all configs (Xray + HAProxy)
hexrift build --all --xray --haproxy --out-dir ./out
# Diff against deployed config
hexrift diff nlA00 --current-dir /etc/xray
# Generate a share link (CDN URL)
hexrift share alice --cdn
# Generate share links for all guests of a user
hexrift share alice --all-guests --bare | clip
# Generate a WireGuard client config
hexrift share alice --wg
Topology options
Beyond the basic hub/exit split:
- HAProxy-less nodes - by default every node runs HAProxy on
:443in front of Xray. Sethaproxy: falseto drop HAProxy and have Xray's Reality inbound bind0.0.0.0:443(or[::]:443when ipv6 is supported) directly.build --haproxythen emits a no-op stubhaproxy.cfgso managed HAProxy service stays up without touching:443. CDN (cdn_xhttp_path) needs HAProxy TLS termination and cannot be combined withhaproxy: false. - All-in-one node - set
routing.hub_default: directto make a hub egress everything itself (directoutbound) instead of routing to exit region. This allows topology with hub node(s) and no exit regions - single node clients connect to that proxies straight to the internet.hub_routesstill apply for per-domain/user exceptions. - Hysteria 2 -
defaults.hub.hysteria:(or a node-levelhysteria:) adds a QUIC/UDP inbound for users withaccess: [hysteria];hexrift share USER --hy2prints thehysteria2://URL. An exit region withprotocol: hysteriais dialed by hubs over Hysteria instead of VLESS+Reality (tuning viadefaults.exit.hysteria→regions[].hysteria→nodes[].hysteria); an exit that defineshysteriaon the region/node serves the listener regardless ofprotocol, so switching a region'sprotocolonly redeploys hub configs. Hysteria needs a real TLS cert: by default HexRift derives a self-signed leaf from the node's Reality key (key_type: ed25519, orecdsa-p256) and pins it (hub outbounds viapinnedPeerCertSha256, share URLs viapinSHA256); setcertificate: {cert_file, key_file}+snito serve an operator-issued cert instead. Xray's Hysteria dialer parrots Chrome's QUIC ClientHello, which cannot negotiate Ed25519 certs, so hubs dialing aned25519exit setdisableChromeParrot; ahysteria2://URL has no such switch, so hub listeners that Xray-based client apps dial should useecdsa-p256. UDP ports must not collide withwireguard/xdnson the same hub. See Topology Schema. - Site-to-site portals -
portals:declares a machine (e.g. a home server) that dials the hubs and opens a reverse tunnel; hub traffic from the portal's member users that matchesroutesegresses there.publish:forwards a hub-bound port into the tunnel for the ingress direction - that port is unauthenticated internet ingress and ignoresportals[].users, so setallow.strict: true(the default) confines portal-side egress to the declaredroutes/publishmatchers and blackholes the rest. See Topology Schema.
Architecture
hexrift/
components/
schema/ # Pydantic models for yaml
derive/ # Identity derivation (UUIDs, shortIds, emails), defaults resolution,
# topology->Xray-fragment construction, WireGuard and Hysteria derivation
keys/ # x25519 + ML-KEM 768 keypair generation and storage
render/ # Xray config builder + HAProxy Jinja2 templates
core/ # BaseApplication / Component / Controller framework
inbounds/ # Pluggable inbound specs (xhttp, cdn, proxy, xdns, wireguard, hysteria) + node contexts
links/ # Pluggable hub→exit link protocols (vless+reality, hysteria): dial context + outbound
shared/ # Cross-component helpers (crypto encoding, Xray/xhttp/hysteria fragments)
templates/ # Jinja2 stubs
haproxy/
wireguard/
Derivation
All identifiers are deterministically derived from the topology:
NAMESPACE UUID= UUID5(UUID(0), namespace)User UUID= UUID5(NAMESPACE_UUID, username)Server UUID= UUID5(USER_UUID,{username}-server)Portal UUID= UUID5(NAMESPACE_UUID,portal/{id})Guest UUID= UUID5(USER_UUID,{label})Hub-exit UUID= UUID5(NAMESPACE_UUID,{hubId}-{exitId})Warp UUID= Hub-exit UUID with 3rd segment replaced byffffGroup shortId= SHA256{groupId}.{namespace}Hub shortId= SHA256{nodeId}.hub.{namespace}Exit shortId= SHA256{nodeId}.exit.{namespace}WireGuard keypair= x25519(HMAC-SHA256(reality_private_key,{identity_uuid}.wireguard.{namespace}))Hysteria certificate= self-signed leaf for the SNI; key seed = HMAC-SHA256(reality_private_key,hysteria-tls.{namespace}) fored25519,hysteria-tls-ecdsa-p256.{namespace}forecdsa-p256; the pin is SHA-256 of its DERHysteria obfs password= base64url(HMAC-SHA256(reality_private_key,hysteria-obfs.{namespace}))
Keys
Keypairs are stored in keys/{nodeId}.yaml. Hub nodes in the same region share the same keypair. Key strings follow the format:
- Decryption (server inbound):
{method}.{mode}.{session_time}[.{padding}].{PRIVATE_KEY_b64} - Encryption (client outbound):
{method}.{mode}.0rtt.{PUBLIC_KEY_b64}
Release files for hexrift 0.13.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| hexrift-0.13.0.tar.gz | 251.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hexrift-0.13.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 359.9 kB
Release files / hexrift-0.13.0.tar.gz
| Download URL | hexrift-0.13.0.tar.gz |
|---|---|
| Size | 251.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e4873c9290dec47e59443911553edb5acab70e0a22893232b99c62677707d166
|
|
BLAKE2b-256 checksum How to use checksums |
3dfa628f70ec9b7950ae59998acfe61c084244921a59eceec0d526ffe661f373
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.16 {"installer":{"name":"uv","version":"0.12.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / hexrift-0.13.0-py3-none-any.whl
| Download URL | hexrift-0.13.0-py3-none-any.whl |
|---|---|
| Size | 108.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2d990c839cbdd3a63dfea033cde3ab1f29e4261c8c61f42f24ddb7ab7f169b1c
|
|
BLAKE2b-256 checksum How to use checksums |
4c7b9d13df44ec4f0e47b978a4736944256b87ce46bcb5c95dca5579b4a9ad68
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.16 {"installer":{"name":"uv","version":"0.12.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|