Appearance
Register a Node
Node self-enrolment posts to POST /v1/register, the contract the plexd agent uses. There is no plexctl register command yet, so this guide drives that endpoint with curl to verify enrolment before handing it off to plexd.
Prerequisites
- A BootstrapToken plaintext — see Issue a bootstrap token.
${PLEXSPHERE_URL}, the${PROJECT_ID}UUID the token was issued for, and the parent${DOMAIN_ID}UUID (for the verification step).- A
${RESOURCE_HANDLE}for the Node — a short, stable name such asedge-router-01. It does not need to exist beforehand: the adoption path below creates the Resource as the Node enrols. - WireGuard userland tools (
wg) for the X25519 keypair.
Steps
Generate the keypair on the Node
shell
wg genkey | tee node.key | wg pubkey > node.pub
PUBKEY="$(cat node.pub)" # 44-char base64, 32-byte X25519 point
NONCE="$(head -c 16 /dev/urandom | base64)"The private half never leaves the Node. A non-X25519 key is rejected with 400 public_key_invalid.
Redeem the token
A fresh Node has no Resource yet, so redeem with the adoption path: send both resource_handle (the lookup handle) and requested_resource_id (the create handle). When resource_handle resolves no existing Resource and requested_resource_id is non-empty, plexsphere synthesises the Resource and the Node in one transaction:
shell
curl --silent --show-error --fail-with-body \
--request POST --header "Content-Type: application/json" \
--data @- "${PLEXSPHERE_URL}/v1/register" <<EOF
{
"project_id": "${PROJECT_ID}",
"resource_handle": "${RESOURCE_HANDLE}",
"requested_resource_id": "${RESOURCE_HANDLE}",
"bootstrap_token": "${BOOTSTRAP_TOKEN}",
"nonce": "${NONCE}",
"public_key": "${PUBKEY}"
}
EOF
# {"node_id":"…","mesh_ip":"100.64.0.1","nsk":"…","signing_public_key":"…","signing_key_id":"…","domain_mesh_cidr":"100.64.0.0/10","peer_snapshot":[]}The response carries the assigned mesh_ip, the per-Node secret (nsk, returned base64-encoded exactly once), the Domain's signing_public_key / signing_key_id, and the domain_mesh_cidr the Node programs its routing table from. A reused nonce returns 403 nonce_collision; a second redemption of the same token returns 403 token_consumed. A bootstrap_token that does not parse — a truncated copy-paste, a hand-written value — returns 422 bootstrap_token_invalid without consuming anything, so a corrected retry uses the same token.
Enrolling against an existing Resource instead. If the Node already has a Resource — provisioned ahead of time rather than adopted on enrolment — send only
resource_handle(the existing handle) and omitrequested_resource_id. An unknown handle withoutrequested_resource_idis rejected with404 resource_not_found.
The peer_snapshot is empty here; the Node becomes a mesh peer a moment after enrolment, when the control plane anchors it and issues its pairwise key. From that point it appears in plexctl peer list and plexctl mesh topology for the Domain.
Verification
Confirm the enrolment from the control plane — the Node is anchored as a peer in its Domain a moment after the 200:
shell
plexctl peer list --domain "${DOMAIN_ID}"
# NODE_ID MESH_IP REACHABILITY
# <node> 100.64.0.1 healthyOn a real Node that also brought up its WireGuard interface, the assigned mesh_ip lands on wg0:
shell
# mesh_ip from the response must fall inside the Domain mesh CIDR
ip -o addr show | grep "$(jq -r .mesh_ip register-response.json)"
# 7: wg0 inet 100.64.0.1/12 scope global wg0\ valid_lft forever preferred_lft foreverSee also
- Issue a bootstrap token — mint the credential first.
- Enrol your first Node — the guided, end-to-end walkthrough (enrol two Nodes, read the mesh, rotate a key) on the local stack.
../../contexts/identity/registration.md— the registration model, including the Resource-adoption branch.