Mainnet call 2026-09-09: beast's two RTX 5090s mine alongside benjy and quadbrat; neuron (cortex inference) is disabled on all three hosts. The node's miners list and lair/quantus's SCRAPE_MINERS gain beast in the lair/quantus counterpart. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CBgs2nSi4H2mdh8kD8vMX5
136 lines
6.7 KiB
Bash
Executable File
136 lines
6.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# One-time provisioning of a mining host for the CI deploy in
|
|
# .gitea/workflows/deploy.yaml (quantus/miner#8). The miner role of
|
|
# lair/quantus's script/infra-setup.sh, moved here so the deployer and its
|
|
# sudoers grant list live in the same repo and cannot drift: the deploy
|
|
# preflights the target's `sudo -n -l` against the SUDO block below.
|
|
#
|
|
# Convention: ~/git/architecture/deployment-gitea-actions.md §1–§2. Run from a
|
|
# workstation with admin (sudo) ssh access to the targets — NOT the gitea_ci
|
|
# account. Idempotent; re-running is a no-op. Skips unreachable hosts.
|
|
#
|
|
# ./deploy/infra-setup.sh --pubkey ~/.ssh/id_gitea_ci.pub
|
|
# ./deploy/infra-setup.sh --pubkey ~/.ssh/id_gitea_ci.pub --miner-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
|
|
# every host's gitea_ci authorized_keys — regenerating it would silently break
|
|
# every other deploy on the fleet.
|
|
#
|
|
# Re-run this whenever the deploy gains a new file to ship or a new privileged
|
|
# command; the preflight fails up front naming the missing paths rather than
|
|
# dying partway through an rsync with "sudo: a password is required".
|
|
set -euo pipefail
|
|
|
|
ADMIN_USER="${ADMIN_USER:-$USER}"
|
|
# Must agree with the deploy matrix in .gitea/workflows/deploy.yaml.
|
|
MINER_HOSTS="${MINER_HOSTS-benjy.hanzalova.internal quadbrat.hanzalova.internal beast.hanzalova.internal}"
|
|
PUBKEY=""
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--pubkey) PUBKEY="$2"; shift 2 ;;
|
|
--miner-hosts) MINER_HOSTS="$2"; shift 2 ;;
|
|
--admin) ADMIN_USER="$2"; shift 2 ;;
|
|
*) echo "unknown arg: $1" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
[ -n "$PUBKEY" ] && [ -r "$PUBKEY" ] || { echo "--pubkey <file> is required and must be readable" >&2; exit 2; }
|
|
|
|
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
|
|
|
|
# All privileged work in one remote `sudo bash`. The runner pubkey is the only
|
|
# dynamic value, passed as $1 (single line, no quoting hazard). The sudoers
|
|
# `\=` are the required escapes — visudo rejects a bare `=` in a command arg.
|
|
# The `*` in an rsync line matches rsync's --server arg vector; the trailing
|
|
# literal destination is what actually bounds the rule.
|
|
ssh "${ADMIN_USER}@${host}" "sudo bash -seu -- '$(cat "$PUBKEY")'" <<'REMOTE'
|
|
PUBKEY="$1"
|
|
|
|
# /bin/bash, NOT nologin. The deploy runs `ssh gitea_ci@host <command>`, and
|
|
# a nologin shell refuses that with "This account is currently not
|
|
# available": the key authenticates, then the command cannot run.
|
|
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 (bench.yaml) 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
|
|
# Rollback: the deploy keeps the previous binary as .prev (cp) and restores it
|
|
# with install when validate fails; cp back would hit "Text file busy" on the
|
|
# running binary, install unlinks the destination first. Deploying from main means a bad commit reaches production;
|
|
# this is what makes that survivable.
|
|
gitea_ci ALL=(root) NOPASSWD: /usr/bin/cp -p /usr/local/bin/quantus-miner /usr/local/bin/quantus-miner.prev
|
|
gitea_ci ALL=(root) NOPASSWD: /usr/bin/install -m 0755 /usr/local/bin/quantus-miner.prev /usr/local/bin/quantus-miner
|
|
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
|
|
}
|
|
|
|
rc=0
|
|
for h in $MINER_HOSTS; do
|
|
provision_miner "$h" || rc=1
|
|
done
|
|
echo
|
|
echo "done. remaining operator steps:"
|
|
echo " 1. confirm RSYNC_SSH_KEY is set in the repo's Actions secrets"
|
|
echo " 2. push to main, or run the deploy workflow manually (mode: validate first)"
|
|
exit $rc
|