Adds a deploy-metrics job shipping a Prometheus scrape drop-in and a provisioned Grafana dashboard to the fleet metrics host, plus the exposure work the exporters needed to be reachable at all. The node's exporter was loopback-only, so --prometheus-external. The miner's already bound 0.0.0.0 unconditionally (crates/metrics builds the listener from [0,0,0,0], no bind option) -- I had recorded it as loopback in the port registry, which was wrong and is corrected. Both exporters are unauthenticated, so each gets a firewalld service opened by a rich rule scoped to the scrape host; that rule, not the bind, is the access boundary. Dashboard panels are built on metric names read off the running node and the miner's source, not guessed. It leads with the states that look healthy but are not: peers at zero, hash rate at zero while the unit is active, GPU devices at zero, jobs never arriving. Two shared-infra changes are in infra-setup.sh rather than the workflow, since other projects depend on both files: prometheus.yml gains scrape_config_files so projects stop sharing one file, and grafana.container gains a dashboards mount it did not have.
99 lines
3.7 KiB
Desktop File
99 lines
3.7 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.
|
|
#
|
|
# 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 planck \
|
|
--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 \
|
|
--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
|