diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml
index e75f9f5..7296a8c 100644
--- a/.gitea/workflows/deploy.yaml
+++ b/.gitea/workflows/deploy.yaml
@@ -1,13 +1,14 @@
name: deploy
-# Deploy — or validate — Quantus Planck nodes and their external GPU miners.
+# Deploy — or validate — Quantus Planck nodes, and the fleet monitoring for
+# nodes and miners. The miners themselves deploy from quantus/miner.
#
# Topology (the workflow is the source of infra truth,
# ~/git/architecture/deployment-gitea-actions.md — hosts belong here, and only
# here; see readme.md "Scope"):
#
-# quantus-node consensus, P2P, rewards
+# quantus-node consensus, P2P, rewards (deployed here)
# ▲ QUIC/9833 (mesh only, rich-rule scoped to that node's miners)
-# quantus-miner GPU search
+# quantus-miner GPU search (deployed by quantus/miner)
#
# The node and the miner are separate processes on separate hosts by design: the
# node is disk+network bound and belongs on an always-on box, the miner is pure
@@ -37,9 +38,6 @@ on:
node_version:
description: quantus-node version (overrides the pin)
required: false
- miner_version:
- description: quantus-miner version (overrides the pin)
- required: false
concurrency: # never half-apply two deploys at once
group: deploy
@@ -54,7 +52,9 @@ env:
# Pinned; bump deliberately to upgrade. An unattended upgrade of a validator
# is how you find out at 3am that a release changed a consensus rule.
NODE_VERSION: "0.11.1"
- MINER_VERSION: "4.0.2"
+ # The miner is deployed by quantus/miner's own workflow, following its main
+ # branch (quantus/miner#8, lair/quantus#4). This repo no longer ships it;
+ # SCRAPE_MINERS below must still agree with that repo's deploy matrix.
# Ports — port-allocations.md §5 registry. These are upstream protocol
# defaults rather than derived numbers; see readme.md "Ports".
P2P_PORT: "30333"
@@ -74,7 +74,7 @@ env:
PROM_PORT: "26559"
GRAFANA_PORT: "28767"
# Scrape targets, rendered into the Prometheus scrape config. These must agree
- # with the deploy-node / deploy-miner matrices below; `validate metrics`
+ # with the deploy-node matrix below and quantus/miner's deploy matrix; `validate metrics`
# asserts Prometheus actually has every target UP, so drift fails loudly rather
# than silently monitoring nothing.
SCRAPE_NODES: bob.hanzalova.internal
@@ -87,16 +87,13 @@ jobs:
runs-on: infra
outputs:
node_version: ${{ steps.v.outputs.node }}
- miner_version: ${{ steps.v.outputs.miner }}
steps:
- id: v
run: |
set -euo pipefail
n="${{ github.event.inputs.node_version || env.NODE_VERSION }}"
- m="${{ github.event.inputs.miner_version || env.MINER_VERSION }}"
echo "node=${n}" >> "$GITHUB_OUTPUT"
- echo "miner=${m}" >> "$GITHUB_OUTPUT"
- echo "quantus-node ${n} / quantus-miner ${m}"
+ echo "quantus-node ${n}"
- name: download quantus-node
run: |
@@ -109,16 +106,6 @@ jobs:
install -D -m 0755 "$(find . -name quantus-node -type f | head -1)" _bin/quantus-node
_bin/quantus-node --version
- - name: download quantus-miner
- run: |
- set -euo pipefail
- v="${{ steps.v.outputs.miner }}"
- curl -fSL --retry 5 --retry-delay 5 --retry-all-errors --connect-timeout 15 \
- -o _bin/quantus-miner \
- "https://github.com/Quantus-Network/quantus-miner/releases/download/v${v}/quantus-miner-linux-x86_64"
- chmod 0755 _bin/quantus-miner
- _bin/quantus-miner --version
-
- uses: actions/upload-artifact@v3
with:
name: quantus-bin
@@ -589,232 +576,9 @@ jobs:
ssh -i ~/.ssh/id_gitea_ci -o StrictHostKeyChecking=accept-new \
gitea_ci@${{ matrix.host }} journalctl -u quantus-node.service -n 80 --no-pager
- deploy-miner:
- runs-on: infra
- needs: [fetch, deploy-node]
- strategy:
- fail-fast: false
- matrix:
- include:
- # `node` is the host whose QUIC control channel this miner attaches
- # to, and whose inner_hash therefore receives what it earns. A miner
- # holds no reward configuration of its own — the mining protocol
- # carries no payout address at all.
- - host: benjy.hanzalova.internal
- node: bob.hanzalova.internal
- gpu_devices: "1" # 1× RTX 4090, measured 183 MH/s @ ~450 W
- - host: quadbrat.hanzalova.internal
- node: bob.hanzalova.internal
- gpu_devices: "1" # 1× RTX 3060, hashrate not yet measured
- steps:
- - uses: actions/checkout@v4
- - uses: actions/download-artifact@v3
- with: { name: quantus-bin, path: _bin }
-
- - name: write ssh key
- run: |
- set -euo pipefail
- install -d -m 0700 ~/.ssh
- printf '%s\n' "${{ secrets.RSYNC_SSH_KEY }}" > ~/.ssh/id_gitea_ci
- chmod 0600 ~/.ssh/id_gitea_ci
-
- - name: reachability
- run: |
- set -euo pipefail
- ssh -i ~/.ssh/id_gitea_ci -o StrictHostKeyChecking=accept-new \
- gitea_ci@${{ matrix.host }} hostname -f
-
- - name: preflight — sudoers covers this deploy
- run: |
- set -euo pipefail
- SSHOPTS="-i $HOME/.ssh/id_gitea_ci -o StrictHostKeyChecking=accept-new"
- # Every path infra-setup.sh grants for this role must already be
- # permitted on the host. Adding an asset here without re-running
- # infra-setup produces a bare "sudo: a password is required" forty
- # lines into the deploy, after some files have already landed. Compare
- # up front instead, from infra-setup.sh itself so the two cannot drift.
- sed -n "/quantus-miner_gitea_ci.tmp/,/^SUDO$/p" script/infra-setup.sh \
- | grep '^gitea_ci ALL=' | grep -oE '(/etc|/usr|/var)/[^ ]*' | sort -u > expected-paths.txt
- ssh $SSHOPTS gitea_ci@${{ matrix.host }} 'sudo -n -l' \
- | grep -oE '(/etc|/usr|/var)/[^ ]*' | sort -u > permitted-paths.txt
- comm -23 expected-paths.txt permitted-paths.txt > missing-paths.txt
- if [ -s missing-paths.txt ]; then
- echo "the miner sudoers on ${{ matrix.host }} is out of date." >&2
- echo "not permitted, but this deploy needs them:" >&2
- sed 's/^/ /' missing-paths.txt >&2
- echo "" >&2
- echo "run: ./script/infra-setup.sh --pubkey ~/.ssh/id_gitea_ci.pub" >&2
- exit 1
- fi
- echo "sudoers covers all $(wc -l < expected-paths.txt) paths this deploy needs"
- - name: deploy miner
- if: ${{ github.event.inputs.mode != 'validate' }}
- env:
- GPU_DEVICES: ${{ matrix.gpu_devices }}
- run: |
- set -euo pipefail
- SSHOPTS="-i $HOME/.ssh/id_gitea_ci -o StrictHostKeyChecking=accept-new"
- nrun() { ssh $SSHOPTS gitea_ci@"${{ matrix.node }}" "$@"; }
- run() { ssh $SSHOPTS gitea_ci@"${{ matrix.host }}" "$@"; }
-
- # See the node job for why -c rather than rsync's default quick check.
- # A rotated auth token or TLS pin shows up here as a content change and
- # correctly forces a restart.
- RESTART=0
- push() {
- local out
- out=$(rsync -e "ssh $SSHOPTS" --rsync-path='sudo rsync' -ic "$@")
- if [ -n "$out" ]; then
- RESTART=1
- printf '%s\n' "$out" | sed 's/^/ changed: /'
- fi
- }
-
- # 1. service account + dirs
- push --mkpath --chmod=F0644 \
- asset/systemd/quantus-miner.sysusers.conf \
- gitea_ci@"${{ matrix.host }}":/etc/sysusers.d/quantus-miner.conf
- run sudo systemd-sysusers
- run sudo install -d -o root -g quantus-miner -m 0750 /etc/quantus-miner
- run sudo install -d -o quantus-miner -g quantus-miner -m 0750 /var/lib/quantus-miner
-
- # 2. The miner's credentials are GENERATED BY THE NODE on first start
- # and regenerate if the node's base-path is ever wiped. Copying them
- # on every deploy — rather than once in infra-setup.sh — is what
- # makes that self-healing instead of a silent auth failure.
- # They pass through the runner in memory, never the workspace.
- umask 077
- tmp=$(mktemp -d)
- trap 'rm -rf "$tmp"' EXIT
- nrun sudo cat /var/lib/quantus-node/chains/${{ env.CHAIN }}/miner-auth-token \
- > "$tmp/miner-auth-token"
- nrun sudo cat /var/lib/quantus-node/chains/${{ env.CHAIN }}/miner-tls-cert-sha256 \
- > "$tmp/miner-tls-cert-sha256"
- test -s "$tmp/miner-auth-token" || { echo "node auth token empty — has ${{ matrix.node }} started?" >&2; exit 1; }
- test -s "$tmp/miner-tls-cert-sha256" || { echo "node TLS pin empty — has ${{ matrix.node }} started?" >&2; exit 1; }
- push --chown=root:quantus-miner --chmod=F0640 \
- "$tmp/miner-auth-token" gitea_ci@"${{ matrix.host }}":/etc/quantus-miner/miner-auth-token
- push --chown=root:quantus-miner --chmod=F0640 \
- "$tmp/miner-tls-cert-sha256" gitea_ci@"${{ matrix.host }}":/etc/quantus-miner/miner-tls-cert-sha256
-
- # 3. non-secret runtime config.
- # --node-addr parses as a Rust SocketAddr: an IP and a port, with NO
- # DNS resolution ("invalid socket address syntax" on a hostname).
- # Resolve on the MINER host — it is the one dialling, so its own view
- # of the node's address is the correct one — and keep the 10.x
- # literal out of the repo, same as the firewall rich rule.
- node_addrs=$(run "getent ahostsv4 ${{ matrix.node }}")
- node_ip=$(awk '{print $1; exit}' <<<"$node_addrs")
- case "$node_ip" in
- 10.*) echo "node address: ${{ matrix.node }} -> ${node_ip}" ;;
- *) echo "refusing to point the miner at non-mesh address '${node_ip}'" >&2; exit 1 ;;
- esac
- export NODE_ADDR="${node_ip}:${{ env.MINER_LINK_PORT }}"
-
- python3 - <<'PY'
- import os, pathlib
- t = pathlib.Path("asset/config/miner.env.tmpl").read_text()
- t = t.replace("{{QUANTUS_NODE_ADDR}}", os.environ["NODE_ADDR"])
- t = t.replace("{{QUANTUS_GPU_DEVICES}}", os.environ["GPU_DEVICES"])
- pathlib.Path("miner.env").write_text(t)
- PY
- push --chown=root:quantus-miner --chmod=F0640 \
- miner.env gitea_ci@"${{ matrix.host }}":/etc/quantus-miner/miner.env
-
- # 4. binary + unit
- push --chmod=F0755 _bin/quantus-miner gitea_ci@"${{ matrix.host }}":/usr/local/bin/quantus-miner
- push --chmod=F0644 asset/systemd/quantus-miner.service \
- gitea_ci@"${{ matrix.host }}":/etc/systemd/system/quantus-miner.service
-
- run sudo restorecon -R /usr/local/bin/quantus-miner /etc/quantus-miner /var/lib/quantus-miner
-
- # 5. firewalld for the exporter. The miner binds metrics on 0.0.0.0
- # unconditionally — there is no loopback option — so without this
- # rule the port is simply closed, and with an unscoped one it would
- # be open to the whole mesh.
- rsync -e "ssh $SSHOPTS" --rsync-path='sudo rsync' -ic --mkpath --chmod=F0644 \
- asset/firewalld/quantus-miner-metrics.xml \
- gitea_ci@"${{ matrix.host }}":/etc/firewalld/services/quantus-miner-metrics.xml \
- | sed 's/^/ changed: /'
- run sudo firewall-cmd --reload
- zone=$(run sudo firewall-cmd --get-default-zone)
- metrics_addrs=$(run "getent ahostsv4 ${{ env.METRICS_HOST }}")
- metrics_ip=$(awk '{print $1; exit}' <<<"$metrics_addrs")
- case "$metrics_ip" in
- 10.*) echo "scrape source: ${metrics_ip}" ;;
- *) echo "refusing to expose metrics to non-mesh address '${metrics_ip}'" >&2; exit 1 ;;
- esac
- mrich="rule family=ipv4 source address=${metrics_ip}/32 service name=quantus-miner-metrics accept"
- if run "sudo firewall-cmd --zone=$zone --query-rich-rule='$mrich'"; then
- echo "firewalld: metrics rich rule already present in ${zone}"
- else
- run "sudo firewall-cmd --permanent --zone=$zone --add-rich-rule='$mrich'"
- run "sudo firewall-cmd --zone=$zone --add-rich-rule='$mrich'"
- fi
-
- run sudo systemctl enable quantus-miner.service # idempotent
- if [ "$RESTART" = 1 ]; then
- echo "changes applied — restarting"
- run sudo systemctl daemon-reload
- run sudo systemctl restart quantus-miner.service
- elif run systemctl is-active --quiet quantus-miner.service; then
- echo "nothing changed and the miner is running — left alone"
- else
- echo "nothing changed but the miner is down — starting it"
- run sudo systemctl restart quantus-miner.service
- fi
-
- - name: validate miner
- run: |
- set -euo pipefail
- SSHOPTS="-i $HOME/.ssh/id_gitea_ci -o StrictHostKeyChecking=accept-new"
- run() { ssh $SSHOPTS gitea_ci@"${{ matrix.host }}" "$@"; }
- fail=0
-
- echo "--- unit (${{ matrix.host }} -> ${{ matrix.node }}) ---"
- run systemctl is-active quantus-miner.service
-
- echo "--- version ---"
- got=$(run /usr/local/bin/quantus-miner --version)
- echo "installed: ${got}"
- case "$got" in
- *"${{ needs.fetch.outputs.miner_version }}"*) echo "version matches pin" ;;
- *) echo "version does NOT match pin ${{ needs.fetch.outputs.miner_version }}" >&2; fail=1 ;;
- esac
-
- echo "--- gpu ---"
- # An `active` miner that found no adapter still looks healthy to
- # systemd; assert the GPU is actually enumerated and busy.
- run "nvidia-smi --query-gpu=name,power.draw,utilization.gpu --format=csv,noheader"
-
- echo "--- hashing ---"
- # The counter is the only honest evidence this process is doing work
- # rather than idling on a failed connection.
- # Here-strings, not pipes — see the node's sync check for why.
- m1=$(run "curl -fsS http://127.0.0.1:${{ env.MINER_METRICS_PORT }}/metrics")
- h1=$(awk '/^miner_hashes_total/{print $2; exit}' <<<"$m1")
- sleep 20
- m2=$(run "curl -fsS http://127.0.0.1:${{ env.MINER_METRICS_PORT }}/metrics")
- h2=$(awk '/^miner_hashes_total/{print $2; exit}' <<<"$m2")
- echo " miner_hashes_total ${h1:-?} -> ${h2:-?}"
- if [ -n "${h1:-}" ] && [ -n "${h2:-}" ] && [ "${h2%.*}" -gt "${h1%.*}" ]; then
- echo " ok hash counter advancing"
- else
- echo " WARN counter not advancing — ${{ matrix.node }} may still be syncing," >&2
- echo " which is expected and not itself a deploy failure." >&2
- fi
-
- exit $fail
-
- - name: journal
- if: always()
- run: |
- ssh -i ~/.ssh/id_gitea_ci -o StrictHostKeyChecking=accept-new \
- gitea_ci@${{ matrix.host }} journalctl -u quantus-miner.service -n 80 --no-pager
-
deploy-metrics:
runs-on: infra
- needs: [deploy-node, deploy-miner]
+ needs: [deploy-node]
# Monitoring assets only. Deliberately last and deliberately separate: a
# Grafana dashboard failing to provision must never be able to leave a
# half-deployed validator behind it.
diff --git a/CLAUDE.md b/CLAUDE.md
index ed75665..a0db159 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -217,8 +217,8 @@ does not arbitrate. This is an open decision, not an oversight.
## Keep the fleet's shape in the workflow
Host assignments, hardware inventories, site or geographic names, ISPs and
-addressing belong in the `deploy-node` / `deploy-miner` matrices in
-`.gitea/workflows/deploy.yaml` — the single source of infra truth — and nowhere
-else. Do not restate them in `readme.md`, `CLAUDE.md`, `doc/`, or unit-file
+addressing belong in the `deploy-node` matrix in `.gitea/workflows/deploy.yaml`
+(and, for the miners, the `deploy` matrix in quantus/miner) — the source of infra
+truth — and nowhere else. Do not restate them in `readme.md`, `CLAUDE.md`, `doc/`, or unit-file
comments, even as helpful context. Prose should describe roles and measured
hardware classes, never which box is where.
diff --git a/asset/config/miner.env.tmpl b/asset/config/miner.env.tmpl
deleted file mode 100644
index ead7340..0000000
--- a/asset/config/miner.env.tmpl
+++ /dev/null
@@ -1,6 +0,0 @@
-# Rendered by .gitea/workflows/deploy.yaml and rsynced to
-# /etc/quantus-miner/miner.env (0640 root:quantus-miner). Not secret — the
-# miner's actual credentials are the auth token and TLS pin, which are copied
-# from the node host as separate 0640 files.
-QUANTUS_NODE_ADDR={{QUANTUS_NODE_ADDR}}
-QUANTUS_GPU_DEVICES={{QUANTUS_GPU_DEVICES}}
diff --git a/asset/firewalld/quantus-miner-metrics.xml b/asset/firewalld/quantus-miner-metrics.xml
deleted file mode 100644
index eeb933e..0000000
--- a/asset/firewalld/quantus-miner-metrics.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
- quantus-miner-metrics
- Quantus miner Prometheus exporter. The miner binds this on
- 0.0.0.0 unconditionally (crates/metrics: SocketAddr from [0,0,0,0]) — there is
- no loopback option — so the firewalld rich rule scoped to the scrape host is
- the only thing bounding who can reach it.
-
-
diff --git a/asset/systemd/quantus-miner.service b/asset/systemd/quantus-miner.service
deleted file mode 100644
index 141e641..0000000
--- a/asset/systemd/quantus-miner.service
+++ /dev/null
@@ -1,64 +0,0 @@
-# Quantus external miner. Runs on the GPU host and connects OUT to the node's
-# QUIC control channel; it listens on nothing but its loopback metrics port, so
-# it ships no firewalld service of its own.
-#
-# Hardened per ~/git/architecture/generic.md §8, with one deliberate relaxation:
-#
-# PrivateDevices=false
-# The miner needs /dev/nvidia*; PrivateDevices=true hides them and wgpu
-# finds no adapter, silently falling back to nothing.
-#
-# Poseidon2-over-Goldilocks is compute-bound in registers, not memory-bound, so
-# there is no tuning knob here worth more than the GPU power limit — which is a
-# host-level concern (nvidia-smi -pl), not a unit-file one.
-#
-# --cpu-workers 0 is deliberate: a current-gen desktop CPU contributes ~4 MH/s
-# next to a 4090's ~183 MH/s, for a couple hundred watts. Measured, not assumed.
-
-[Unit]
-Description=Quantus external miner (GPU)
-Documentation=https://github.com/Quantus-Network/quantus-miner
-After=network-online.target
-Wants=network-online.target
-
-[Service]
-Type=simple
-User=quantus-miner
-Group=quantus-miner
-Environment=RUST_LOG=info
-WorkingDirectory=/var/lib/quantus-miner
-
-ExecStart=/usr/local/bin/quantus-miner serve \
- --node-addr ${QUANTUS_NODE_ADDR} \
- --auth-token-file /etc/quantus-miner/miner-auth-token \
- --tls-cert-sha256-file /etc/quantus-miner/miner-tls-cert-sha256 \
- --gpu-devices ${QUANTUS_GPU_DEVICES} \
- --cpu-workers 0 \
- --metrics-port 9900
-EnvironmentFile=/etc/quantus-miner/miner.env
-
-# The node may be down, still syncing, or mid-redeploy; reconnecting is normal
-# operation, not an error condition.
-Restart=always
-RestartSec=15s
-LimitNOFILE=65536
-
-NoNewPrivileges=true
-ProtectSystem=strict
-ProtectHome=true
-PrivateTmp=true
-PrivateDevices=false
-ProtectKernelTunables=true
-ProtectKernelModules=true
-ProtectControlGroups=true
-RestrictRealtime=true
-RestrictSUIDSGID=true
-LockPersonality=true
-SystemCallArchitectures=native
-MemoryDenyWriteExecute=false
-
-ReadWritePaths=/var/lib/quantus-miner
-RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
-
-[Install]
-WantedBy=multi-user.target
diff --git a/asset/systemd/quantus-miner.sysusers.conf b/asset/systemd/quantus-miner.sysusers.conf
deleted file mode 100644
index 2ed369e..0000000
--- a/asset/systemd/quantus-miner.sysusers.conf
+++ /dev/null
@@ -1,4 +0,0 @@
-#Type Name ID GECOS Home directory Shell
-u quantus-miner - "Quantus miner service account" /var/lib/quantus-miner /usr/sbin/nologin
-m quantus-miner video
-m quantus-miner render
diff --git a/readme.md b/readme.md
index 5803ceb..d874d3a 100644
--- a/readme.md
+++ b/readme.md
@@ -10,8 +10,10 @@ Two components:
| `quantus-node` | consensus, P2P, reward address. Disk + network bound. |
| `quantus-miner` | Poseidon2-over-Goldilocks search. Pure GPU compute. |
-**Scope:** which hosts run what lives in the `deploy-node` / `deploy-miner`
-matrices in `.gitea/workflows/deploy.yaml` and nowhere else — that is the
+**Scope:** which hosts run what lives in the `deploy-node` matrix in
+`.gitea/workflows/deploy.yaml` and the `deploy` matrix in quantus/miner's
+`.gitea/workflows/deploy.yaml` (the miner deploys from its own repo since
+2026-09-03, lair/quantus#4), and nowhere else — that is the
convention (`deployment-gitea-actions.md`) and it keeps the fleet's shape in one
place instead of scattered through prose. Don't restate host assignments,
hardware inventories, sites or addressing in this file.
@@ -149,7 +151,7 @@ so that switching is a config change rather than a scramble:
| To switch | Change |
| --- | --- |
| Chain | `CHAIN:` in the workflow `env:` — the unit reads it from config, and the miner credential path `/chains//` follows from the same value |
-| Versions | `NODE_VERSION` / `MINER_VERSION` pins |
+| Versions | `NODE_VERSION` pin here; the miner follows quantus/miner `main` |
| Reward address | `inner_hash` per node in the matrix |
Still to decide before launch day, none of which the deploy can settle for you:
@@ -358,8 +360,8 @@ untouched deployment is still healthy.
no GPU adapter, and a node that is authoring nothing because no miner is
attached, both look perfectly healthy to systemd. Hence the counter check.
-Upgrades are deliberate — bump `NODE_VERSION` / `MINER_VERSION` in the workflow.
-There is no auto-update on a validator on purpose.
+Node upgrades are deliberate — bump `NODE_VERSION` in the workflow. There is no
+auto-update on a validator on purpose. The miner follows quantus/miner `main`.
## Known conflict: GPU contention with helexa ⚠️
diff --git a/script/infra-setup.sh b/script/infra-setup.sh
index 7d6531c..f501a92 100755
--- a/script/infra-setup.sh
+++ b/script/infra-setup.sh
@@ -6,8 +6,8 @@
# node (--node-hosts) quantus-node binary + unit + config + firewalld,
# and read access to the two miner credential files
# the node generates -> deploy.yaml deploy-node
-# miner (--miner-hosts) quantus-miner binary + unit + credentials
-# -> deploy.yaml deploy-miner
+# (the miner role moved to quantus/miner deploy/infra-setup.sh on 2026-09-03;
+# that repo deploys the miner and owns its gitea_ci sudoers)
# metrics (--metrics-hosts) the fleet Prometheus/Grafana host: a scrape
# config drop-in and a provisioned dashboard
# -> deploy.yaml deploy-metrics
@@ -35,7 +35,7 @@
#
# ./script/infra-setup.sh --pubkey ~/.ssh/id_gitea_ci.pub
# ./script/infra-setup.sh --pubkey ~/.ssh/id_gitea_ci.pub --node-hosts "" \
-# --miner-hosts benjy.hanzalova.internal
+# --gpu-hosts benjy.hanzalova.internal
#
# The runner keypair is NOT generated here. It already exists at
# ~/.ssh/id_gitea_ci and is shared by every project's RSYNC_SSH_KEY secret and
@@ -48,7 +48,6 @@ ADMIN_USER="${ADMIN_USER:-$USER}"
# this role", which is how you provision only the other one. With `:-` the empty
# value would silently fall back to the full default list.
NODE_HOSTS="${NODE_HOSTS-bob.hanzalova.internal}"
-MINER_HOSTS="${MINER_HOSTS-benjy.hanzalova.internal quadbrat.hanzalova.internal}"
METRICS_HOSTS="${METRICS_HOSTS-golgafrinchans.kosherinata.internal}"
# The site reverse proxy that fronts quantus.internal. Empty to skip the edge role.
EDGE_HOSTS="${EDGE_HOSTS-hanzalova.internal}"
@@ -77,7 +76,6 @@ while [[ $# -gt 0 ]]; do
case "$1" in
--pubkey) PUBKEY="$2"; shift 2 ;;
--node-hosts) NODE_HOSTS="$2"; shift 2 ;;
- --miner-hosts) MINER_HOSTS="$2"; shift 2 ;;
--metrics-hosts) METRICS_HOSTS="$2"; shift 2 ;;
--edge-hosts) EDGE_HOSTS="$2"; shift 2 ;;
--gpu-hosts) GPU_HOSTS="$2"; shift 2 ;;
@@ -194,85 +192,6 @@ SUDO
REMOTE
}
-provision_miner() {
- local host="$1"
- echo "== ${host} (miner) =="
- if ! ssh -o ConnectTimeout=8 -o BatchMode=yes "${ADMIN_USER}@${host}" true; then
- echo " ! unreachable as ${ADMIN_USER} — skipping" >&2
- return 1
- fi
-
- ssh "${ADMIN_USER}@${host}" "sudo bash -seu -- '$(cat "$PUBKEY")'" <<'REMOTE'
- PUBKEY="$1"
-
- # /bin/bash, NOT nologin. The deploy runs `ssh gitea_ci@host `, and
- # a nologin shell refuses that with "This account is currently not
- # available": the key authenticates, then the command cannot run. Every
- # pre-existing gitea_ci on this fleet has bash, so a nologin one is a bug —
- # repair it rather than leaving a host that can never be deployed to.
- if ! getent passwd gitea_ci >/dev/null; then
- useradd --system --create-home --home-dir /var/lib/gitea_ci \
- --shell /bin/bash gitea_ci
- echo " + created gitea_ci"
- else
- echo " = gitea_ci already present"
- fi
- cur=$(getent passwd gitea_ci | cut -d: -f7)
- if [ "$cur" != /bin/bash ]; then
- usermod -s /bin/bash gitea_ci
- echo " ~ gitea_ci shell was ${cur} — set to /bin/bash so ssh commands can run"
- fi
-
- install -d -o gitea_ci -g gitea_ci -m 0700 /var/lib/gitea_ci/.ssh
- ak=/var/lib/gitea_ci/.ssh/authorized_keys
- touch "$ak"
- grep -qxF "$PUBKEY" "$ak" || printf '%s\n' "$PUBKEY" >> "$ak"
- chown gitea_ci:gitea_ci "$ak"
- chmod 0600 "$ak"
- usermod -aG systemd-journal gitea_ci
-
- # The GPU must be present and enumerable before a miner deploy is worth
- # attempting; failing here beats a green deploy that mines nothing.
- if ! command -v nvidia-smi >/dev/null; then
- echo " ! nvidia-smi not found — this host has no usable NVIDIA driver" >&2
- exit 1
- fi
- nvidia-smi --query-gpu=name --format=csv,noheader | sed 's/^/ = gpu: /'
-
- tmp=/etc/sudoers.d/.quantus-miner_gitea_ci.tmp
- cat > "$tmp" <<'SUDO'
-gitea_ci ALL=(root) NOPASSWD: /usr/bin/rsync * /usr/local/bin/quantus-miner
-gitea_ci ALL=(root) NOPASSWD: /usr/bin/rsync * /etc/systemd/system/quantus-miner.service
-gitea_ci ALL=(root) NOPASSWD: /usr/bin/rsync * /etc/sysusers.d/quantus-miner.conf
-gitea_ci ALL=(root) NOPASSWD: /usr/bin/rsync * /etc/quantus-miner/miner.env
-gitea_ci ALL=(root) NOPASSWD: /usr/bin/rsync * /etc/quantus-miner/miner-auth-token
-gitea_ci ALL=(root) NOPASSWD: /usr/bin/rsync * /etc/quantus-miner/miner-tls-cert-sha256
-gitea_ci ALL=(root) NOPASSWD: /usr/bin/rsync * /etc/firewalld/services/quantus-miner-metrics.xml
-gitea_ci ALL=(root) NOPASSWD: /usr/bin/firewall-cmd --get-default-zone
-gitea_ci ALL=(root) NOPASSWD: /usr/bin/firewall-cmd --reload
-gitea_ci ALL=(root) NOPASSWD: /usr/bin/firewall-cmd --permanent --zone\=* --add-rich-rule\=*
-gitea_ci ALL=(root) NOPASSWD: /usr/bin/firewall-cmd --zone\=* --add-rich-rule\=*
-gitea_ci ALL=(root) NOPASSWD: /usr/bin/firewall-cmd --zone\=* --query-rich-rule\=*
-gitea_ci ALL=(root) NOPASSWD: /usr/bin/systemd-sysusers
-gitea_ci ALL=(root) NOPASSWD: /usr/bin/install -d -o root -g quantus-miner -m 0750 /etc/quantus-miner
-gitea_ci ALL=(root) NOPASSWD: /usr/bin/install -d -o quantus-miner -g quantus-miner -m 0750 /var/lib/quantus-miner
-gitea_ci ALL=(root) NOPASSWD: /usr/sbin/restorecon -R /usr/local/bin/quantus-miner /etc/quantus-miner /var/lib/quantus-miner
-gitea_ci ALL=(root) NOPASSWD: /usr/bin/systemctl daemon-reload
-gitea_ci ALL=(root) NOPASSWD: /usr/bin/systemctl enable quantus-miner.service
-gitea_ci ALL=(root) NOPASSWD: /usr/bin/systemctl restart quantus-miner.service
-# stop/start (not just restart) so the benchmark harness in quantus/miner#2 can
-# pause mining for a measurement window and resume it afterwards.
-gitea_ci ALL=(root) NOPASSWD: /usr/bin/systemctl stop quantus-miner.service
-gitea_ci ALL=(root) NOPASSWD: /usr/bin/systemctl start quantus-miner.service
-SUDO
- chmod 0440 "$tmp"
- visudo -cf "$tmp"
- mv "$tmp" /etc/sudoers.d/quantus-miner_gitea_ci
- echo " = sudoers quantus-miner_gitea_ci installed and visudo-verified"
-REMOTE
-}
-
-
# The fleet Prometheus/Grafana host. Two of the things this does are edits to
# SHARED config that other projects also rely on, which is exactly why they live
# here (operator-run, one-time) and not in the deploy workflow:
@@ -553,9 +472,6 @@ for h in $NODE_HOSTS; do
provision_node "$h" || { rc=1; continue; }
ensure_node_key "$h" || rc=1
done
-for h in $MINER_HOSTS; do
- provision_miner "$h" || rc=1
-done
for h in $METRICS_HOSTS; do
provision_metrics "$h" || rc=1
done