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