Some checks failed
deploy / fetch (push) Successful in 17s
deploy / deploy-miner (1, quadbrat.hanzalova.internal, bob.hanzalova.internal) (push) Has been skipped
deploy / deploy-node (bob.hanzalova.internal, 0x134e73f06fa9bdb1dbfa909e149c563f5860ceb71a0e7307918f7033970edf59, benjy.hanzalova.internal quadbrat.hanzalova.internal, --public-addr /dns4/nh.thgttg.com/tcp/30333, --rpc-external --rpc-methods safe --rpc-cors all… (push) Failing after 31s
deploy / deploy-miner (1, benjy.hanzalova.internal, bob.hanzalova.internal) (push) Has been skipped
deploy / deploy-metrics (push) Has been skipped
Substrate binds JSON-RPC to loopback and makes opening it deliberately awkward, which is correct for a validator. rpc_expose in the node matrix takes the complete flag string; empty keeps it on loopback. --rpc-methods safe is the load-bearing flag. It permits every state read and author_submitExtrinsic (signed, so no key exposure) while blocking author_rotateKeys, system_addReservedPeer and the rest. --rpc-methods auto happens to downgrade once listening externally, but relying on that makes the safety depend on a flag nobody wrote down. The firewalld rich rule is scoped to the node's own /16, derived from its own resolved address at deploy time -- so no subnet literal enters this repo and the rule follows the host between sites. Loopback is whitelisted from rate limiting because the arena exporter runs beside the node and makes ~4 calls every 2s, which is 120/min -- enough to throttle itself against a naive limit. validate now asserts the bind matches intent in both directions: nothing listening while rpc_expose is set is a failure, and loopback-only is reported as configuration rather than silence.
105 lines
4.0 KiB
Desktop File
105 lines
4.0 KiB
Desktop File
# Quantus Planck 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 (Planck, 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_RPC_EXPOSE \
|
|
--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
|