mirror of
https://github.com/GoodOlClint/PSProxmoxVE.git
synced 2026-09-04 03:05:32 +00:00
f48f0ea9ae
The nested PVE guests live on an isolated VLAN with no route to the runner network (ADR 0032, homelab repo), and the runner-hosted Docker services died with each ephemeral ARC pod anyway. A small cloud-image VM in the ci pool now serves NFS, iSCSI, and the auto-install answer files from inside the sandbox. Includes fixes from Codex and in-house review: bpg provider >= 0.79.0, serial console for the resized cloud image, key-based SSH (cloud images refuse password auth), CIDR validation, storage VM in the headroom check, docker socket mounts removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
108 lines
4.1 KiB
Bash
108 lines
4.1 KiB
Bash
#!/usr/bin/env bash
|
|
# Configures the shared services VM inside the CI sandbox VLAN: NFS export,
|
|
# iSCSI target, and the HTTP answer server the PVE auto-installers fetch
|
|
# from. Idempotent — safe to re-run against an already-configured VM.
|
|
#
|
|
# Usage: setup-storage-server.sh <storage-vm-ip> <ssh-private-key-path> <iscsi-iqn> <answer-dir>
|
|
# <answer-dir> is the local directory holding default.toml and answers/.
|
|
|
|
set -euo pipefail
|
|
|
|
STORAGE_IP="${1:?Usage: setup-storage-server.sh <ip> <ssh-key> <iqn> <answer-dir>}"
|
|
SSH_KEY="${2:?missing SSH private key path}"
|
|
ISCSI_IQN="${3:?missing iSCSI IQN}"
|
|
ANSWER_DIR="${4:?missing answer directory}"
|
|
|
|
# Same pinned image the Terraform-managed runner container used previously.
|
|
ANSWER_SERVER_IMAGE="slothcroissant/proxmox-auto-installer-server@sha256:0f45d7bfe6e3cc76aa00fc578e40b80b9054e377db18a79122866fe5522bc7ed"
|
|
|
|
SSH_OPTS="-i ${SSH_KEY} -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR"
|
|
|
|
echo "=== Configuring storage services on ${STORAGE_IP} ==="
|
|
|
|
# Cloud-init may still be running on first boot; wait for SSH to accept the
|
|
# key login it installs. The last attempt's stderr is kept so an auth failure
|
|
# is distinguishable from an unreachable host.
|
|
for i in $(seq 1 60); do
|
|
if ssh ${SSH_OPTS} -o ConnectTimeout=5 "ubuntu@${STORAGE_IP}" true 2>/tmp/storage-ssh-err; then
|
|
break
|
|
fi
|
|
if [[ $i -eq 60 ]]; then
|
|
echo "ERROR: storage VM at ${STORAGE_IP} not reachable over SSH" >&2
|
|
cat /tmp/storage-ssh-err >&2
|
|
exit 1
|
|
fi
|
|
sleep 5
|
|
done
|
|
|
|
# sshd accepts logins before cloud-init's first-boot work (and its apt/dpkg
|
|
# locks) is finished; a degraded cloud-init result is fine for our purposes,
|
|
# and a hung cloud-init must fail fast, not eat the job timeout.
|
|
timeout 300 ssh ${SSH_OPTS} "ubuntu@${STORAGE_IP}" \
|
|
"sudo cloud-init status --wait" || true
|
|
|
|
echo "Copying answer files..."
|
|
ssh ${SSH_OPTS} "ubuntu@${STORAGE_IP}" \
|
|
"sudo mkdir -p /opt/answer-server && sudo rm -rf /opt/answer-server/answers && sudo chown ubuntu /opt/answer-server"
|
|
scp ${SSH_OPTS} -r \
|
|
"${ANSWER_DIR}/default.toml" "${ANSWER_DIR}/answers" \
|
|
"ubuntu@${STORAGE_IP}:/opt/answer-server/"
|
|
|
|
ssh ${SSH_OPTS} "ubuntu@${STORAGE_IP}" \
|
|
"sudo ISCSI_IQN='${ISCSI_IQN}' ANSWER_SERVER_IMAGE='${ANSWER_SERVER_IMAGE}' bash -s" <<'REMOTE'
|
|
set -euo pipefail
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
|
|
# cloud-init's first-boot apt activity can hold the dpkg lock briefly
|
|
for i in $(seq 1 30); do
|
|
apt-get update -qq >/dev/null 2>&1 && break
|
|
sleep 5
|
|
done
|
|
apt-get install -y -qq nfs-kernel-server tgt docker.io >/dev/null
|
|
|
|
# Ubuntu's nfs-kernel-server package does not create /etc/exports.d
|
|
mkdir -p /etc/exports.d /srv/nfs/shared /srv/iscsi
|
|
echo '/srv/nfs/shared *(rw,sync,no_subtree_check,no_root_squash)' > /etc/exports.d/pvetest.exports
|
|
exportfs -ra
|
|
|
|
if [ ! -f /srv/iscsi/lun0.img ]; then
|
|
truncate -s 10G /srv/iscsi/lun0.img
|
|
fi
|
|
cat > /etc/tgt/conf.d/pvetest.conf <<CONF
|
|
<target ${ISCSI_IQN}>
|
|
backing-store /srv/iscsi/lun0.img
|
|
</target>
|
|
CONF
|
|
|
|
systemctl enable --now nfs-kernel-server tgt >/dev/null || {
|
|
journalctl -u nfs-kernel-server -u tgt --no-pager -n 30 >&2
|
|
exit 1
|
|
}
|
|
systemctl restart tgt
|
|
|
|
docker rm -f pvetest-answer-server >/dev/null 2>&1 || true
|
|
docker run -d --name pvetest-answer-server --restart unless-stopped \
|
|
--network host \
|
|
-v /opt/answer-server/answers:/app/answers \
|
|
-v /opt/answer-server/default.toml:/app/default.toml \
|
|
"${ANSWER_SERVER_IMAGE}" >/dev/null
|
|
|
|
# Ordering requirement: the PVE installers fetch answers as soon as this
|
|
# script returns, so :8000 must accept connections before exit (any HTTP
|
|
# status counts as listening).
|
|
for i in $(seq 1 20); do
|
|
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 2 http://127.0.0.1:8000/ || true)
|
|
[ "$code" != "000" ] && break
|
|
if [ "$i" -eq 20 ]; then
|
|
echo "ERROR: answer server did not start listening on :8000" >&2
|
|
docker logs pvetest-answer-server 2>&1 | tail -20 >&2
|
|
exit 1
|
|
fi
|
|
sleep 3
|
|
done
|
|
|
|
echo "Services ready: NFS /srv/nfs/shared, iSCSI ${ISCSI_IQN}, answer server :8000"
|
|
REMOTE
|
|
|
|
echo "Storage server configuration complete."
|