Skip to the content.

Getting Started

This guide covers the supported server path: build, package install, starting a new network (Genesis), joining an existing network (server onboarding), and diagnosing the services. It ends with the current blockers you must know before planning a deployment.

Prerequisites

A Linux server (Debian 12+ or Ubuntu 22.04+) with a public IP and a DNS zone you control for the discovery base domain. For a packaged install the machine needs Python 3 for nexus-bootstrap.

To build from source you also need:

The normal Linux build compiles TPM2-TSS from source; you do not install a system TPM2-TSS package. See Building for details.

Open the ports listed in Ports and Firewall. In short: TCP 9100, UDP 51940 (mesh and hole punching share this port), UDP 9102, UDP 3478, and, for DNS-serving nodes, UDP+TCP 53 mapped to local 5335.

Build the server

git clone https://github.com/lemonade-sdk/lemonade-nexus.git
cd lemonade-nexus
cmake -S . -B build -G Ninja \
  -DCMAKE_BUILD_TYPE=Release \
  -DBUILD_TESTING=ON
cmake --build build --parallel

The server executable is build/projects/LemonadeNexus/lemonade-nexus.

Hardware-dependent tests skip when their required environment is absent. Skipped tests do not establish hardware qualification.

Install the Debian/Ubuntu package

For a systemd deployment, build the package and install it:

cpack --config build/CPackConfig.cmake -G DEB -B build/packages
sudo apt install ./build/packages/lemonade-nexus-*.deb

The package installs:

The install enables the services without starting them. Start them only after the configuration below is in place.

Start a new network (Genesis)

Genesis starts a new network. Use the packaged bootstrap command:

sudo nexus-bootstrap \
  --release-signing-pubkey 'YOUR_RELEASE_SIGNING_PUBLIC_KEY'

--release-signing-pubkey is the Ed25519 public key that verifies your release manifests, encoded as 64 hex characters or base64 for 32 bytes. Generate one with:

python3 scripts/generate_release_signing_key.py

Bootstrap creates the node identities, pins the root and Genesis public keys, and writes:

The on-disk values:

Config key Value for the Genesis server
root_pubkey The node identity public key (hex Ed25519). This is the mesh root; every other server must use the same value.
genesis_pubkey The node gossip public key (base64 Ed25519). It pins the Genesis bootstrap anchor, whose unilateral security authority ends at Epoch 1 activation.
release_signing_pubkey The release-signing key you supplied (or, for --test-release-key, the node identity).

If this host serves public authoritative DNS, add --install-dns-nat to map UDP/TCP 53 to 5335 with nftables. That option requires nft and ip; --wan-interface selects the external interface when auto-detection is ambiguous. You can also configure the mapping on your router. See Ports and Firewall.

For a disposable development network, sudo nexus-bootstrap --test-release-key uses the node identity as the test release key. It does not bypass attestation requirements or enable Tier 1 qualification.

Then start the server:

sudo systemctl start lemonade-nexus.service
systemctl status lemonade-nexus.service nexus-attestd.service
sudo journalctl -u lemonade-nexus.service -u nexus-attestd.service -f

The server unit also requests nexus-attestd, so starting the server pulls the helper in.

A successful bootstrap or daemon start does not mean that Epoch 1 has formed. The shipped attestation profile is incomplete by design, so no node can currently reach Tier 1 with it. See current blockers and Security — current limitations.

Join an existing network (server onboarding)

Use server onboarding instead of initializing another Genesis. A candidate server asks an existing server for admission over the public API and installs the returned network-bound certificate.

Trust values the candidate needs

Value How the candidate obtains it Where it goes
root_pubkey (hex Ed25519) Out of band, before onboarding. On the Genesis server it is the Identity pubkey printed by --first-run; it is also in the Genesis server’s protected configuration. Passed as --root-pubkey; the client verifies the bundle against it. Onboarding confirms it but never writes the config.
genesis_pubkey (base64 Ed25519) The onboarding bundle authenticates it: the certificate’s network ID must derive from the bundle’s Genesis key. No separate out-of-band exchange is needed. The operator sets it in the candidate’s config before start: copy it from the Genesis server at bootstrap time, or from the onboarding client’s report (the bundle’s Genesis binding is verified by the client, so the printed value is authenticated). Onboarding warns if the config’s value differs from the admitted mesh’s.
release_signing_pubkey Not part of the bundle. Configure it separately with the key that verifies the releases you will run. Candidate’s config. Required for a normal daemon start.

Do not copy security state (certificates, keypairs, epoch stores) from another server to make a candidate join. Onboarding produces the candidate’s own certificate and keys.

Run the onboarding client

From the candidate server:

sudo runuser -u lemonade-nexus -- /usr/bin/lemonade-nexus \
  --data-root /var/lib/lemonade-nexus/data \
  --config /var/lib/lemonade-nexus/lemonade-nexus.json \
  --root-pubkey <EXPECTED_ROOT_HEX> \
  --onboard-server <existing-server-fqdn>:9100

Notes on the options:

The candidate must have a data directory (run --first-run once as the service user, or let the packaged flow create it). The candidate’s platform evidence, when the host can produce it, is attached to the admission request and bound to the challenge nonce. A host without usable evidence receives a Tier 2 certificate; that is not a failure.

What happens during onboarding

  1. The client sends a challenge request with its gossip public key.
  2. It answers with a signed admission request (identity proof of possession), optionally with platform evidence and an enrollment token.
  3. On the existing server, an administrator approves the pending request on the private API (POST /api/onboard/approve/<request_id>, reachable over the mesh), or the request was admitted immediately via its token.
  4. The client polls, then verifies the returned bundle: the root key matches the pinned --root-pubkey, the certificate is bound to the candidate’s public key and signed by the root, and the bundle’s Genesis binding holds.
  5. The client persists only the certificate to <data_root>/identity/server_cert.json. It never modifies the config file (the trust anchors stay root-protected); it prints the approved anchors for verification and the recommended seed peers for the operator to apply (the address it just reached is recommended first).

It then prints the server ID, the installed paths, and the command to start the server.

After onboarding

  1. Edit the protected configuration with sudoedit: verify release_signing_pubkey is set (it is not delivered by onboarding), and apply the recommended seed_peers from the onboarding report (if any). Onboarding itself never writes this file.
  2. Start the packaged service:

    sudo systemctl start lemonade-nexus.service
    
  3. Confirm the server registers its discovery records and gossips with the seeded peers.

Admission grants a server identity, not Tier 1 membership. Tier 1 authority is established only by the epoch protocol described in Security.

TLS and DNS prerequisites

Service diagnostics

# Service state
systemctl status lemonade-nexus.service nexus-attestd.service
systemctl status nexus-dns-nat.service   # only when --install-dns-nat was used

# Logs
sudo journalctl -u lemonade-nexus.service -f
sudo journalctl -u nexus-attestd.service -f

# Startup lines to look for
#   "All services started. HTTPS:9100, PrivateHTTPS:..., UDP:51940, ..."
#   "Public API withheld: no TLS certificate yet ..."  (expected on first boot;
#    the background ACME thread brings the listener up once a cert is issued)
#   "Attestation profile v1 is incomplete: ..." (expected; see current blockers)

# Listener check
ss -ltnup | grep -E '9100|9101|51940|9102|3478|9103|5335'

If the startup log contains SECURITY: No tunnel_bind_ip configured — private API routes are exposed on the public HTTP server, the private API is not isolated on the mesh. Route authentication still applies, but network isolation does not. Review the server’s mesh configuration before exposing it.

Current blockers

These are implementation limits, not settings an operator can disable:

Everything in this guide that does not depend on those two items works as documented: mesh transport, client join, discovery, certificates, and Tier 2 server operation.