Files
quantus/asset/systemd/quantus-node.service
T
Rob ThijssenandClaude Fable 5.1 6276a0bf98 deploy: run the node from a chain spec file with genesis assertion, bootnodes as reserved peers, raised peer limits
Mainnet launches 2026-09-10 09:09 UTC with the chain spec published as a
file before any binary embeds it. The node deploy can now run from
/etc/quantus-node/chainspec.json (CHAINSPEC_URL or the chainspec_url
input), asserts the started node's block-0 hash against EXPECTED_GENESIS
(required with a spec: it is the only defence against a look-alike), and
prints the spec id, which names <base-path>/chains/<id>/ and is what the
miner deploy must be pointed at; validate checks the miner credentials
exist under it. BOOTNODES are dialled and held as reserved peers.

The unit raises --out-peers to 32 and --in-peers to 64: bob sits at the
default cap of 40 peers today, and launch is a propagation race
(quantus/chain#1). infra-setup grants the runner the chain spec path
(applied to bob 2026-09-09).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBgs2nSi4H2mdh8kD8vMX5
2026-09-09 08:05:00 +03:00

108 lines
4.1 KiB
Desktop File

# Quantus node, validator role, external-miner mode.
#
# Hardened per ~/git/architecture/generic.md §8. Two knobs are deliberately
# relaxed; both are load-bearing, do not "tidy" them back:
#
# MemoryDenyWriteExecute=false
# Substrate JITs the on-chain wasm runtime through wasmtime and needs W|X
# pages. With this true the node aborts during block import.
#
# Type=simple (not notify)
# quantus-node does not sd_notify(READY=1). Type=notify would block
# `systemctl restart` until TimeoutStartSec expires
# (deployment-gitea-actions.md §6).
#
# The reward preimage arrives via EnvironmentFile only because the node exposes
# no environment-variable form for any flag (`--help` carries no `[env:]`
# annotations) and the value is per-deployment while this unit is a static
# asset. It is NOT hidden and does not need to be: the node embeds the preimage
# verbatim in the PreRuntime digest of every block it authors, so it is public
# on-chain from your first block onward. Do not add secret-handling around it.
# $QUANTUS_PUBLIC_ADDR is deliberately a bare $VAR, not ${VAR}: systemd
# word-splits the former, so a value of "--public-addr /dns4/host/tcp/30333"
# becomes two arguments and an EMPTY value becomes zero arguments. That lets one
# unit serve both a publicly-dialable node and a mesh-only one.
#
# --prometheus-external binds the exporter on all interfaces instead of
# loopback, so the fleet Prometheus (a different host, a different site) can
# scrape it. The exporter is unauthenticated, so what actually bounds access is
# the firewalld rich rule the deploy scopes to the scrape host — not the bind.
#
# $QUANTUS_RPC_EXPOSE is the same bare-$VAR pattern: empty leaves the JSON-RPC
# server on loopback (the default and the right choice for a validator), a
# non-empty value opens it. Access is bounded by a firewalld rich rule scoped to
# the site subnet, never by the bind.
#
# It matters behind NAT: without it libp2p advertises the address it can see —
# a 10.x LAN address — which no external peer can dial. A port forward alone
# gets packets in but never tells anyone to send them.
[Unit]
Description=Quantus node (validator, external-miner mode)
Documentation=https://github.com/Quantus-Network/chain
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=quantus-node
Group=quantus-node
Environment=RUST_LOG=info
EnvironmentFile=/etc/quantus-node/node.env
WorkingDirectory=/var/lib/quantus-node
ExecStart=/usr/local/bin/quantus-node \
--validator \
--chain ${QUANTUS_CHAIN} \
--name baba-gorchitsa \
--base-path /var/lib/quantus-node \
--node-key-file /var/lib/quantus-node/node_key.p2p \
--rewards-inner-hash ${QUANTUS_INNER_HASH} \
--miner-listen-port 9833 \
--port 30333 \
$QUANTUS_PUBLIC_ADDR \
$QUANTUS_BOOTNODES \
$QUANTUS_RPC_EXPOSE \
--out-peers 32 \
--in-peers 64 \
--prometheus-port 9615 \
--prometheus-external \
--max-blocks-per-request 64 \
--sync full
Restart=always
RestartSec=10s
# RocksDB needs time to flush cleanly; a SIGKILL mid-compaction means a slow
# (or corrupt) recovery on next start.
TimeoutStopSec=300
KillSignal=SIGINT
LimitNOFILE=65536
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictRealtime=true
RestrictSUIDSGID=true
LockPersonality=true
SystemCallArchitectures=native
MemoryDenyWriteExecute=false
ReadWritePaths=/var/lib/quantus-node
# AF_NETLINK is REQUIRED, not decoration. libp2p calls getifaddrs() to enumerate
# interfaces, and getifaddrs() opens a NETLINK_ROUTE socket. Without it the node
# still starts and reports `active`, but logs
# failed to fetch network interfaces error=GetIfAddrsError("getifaddrs", -1)
# litep2p started with no listen addresses, cannot accept inbound connections
# and never binds --port at all — a silently undialable peer. Verified on a live
# host: adding AF_NETLINK binds 30333 on both stacks; removing it unbinds.
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
[Install]
WantedBy=multi-user.target