provisioning-catalog/components/lian_node/taskserv/install-lian_node.sh

363 lines
15 KiB
Bash
Raw Permalink Normal View History

#!/bin/bash
# install-lian_node.sh — taskserv installer for a persistent BuildKit node.
# Runs on the target Debian/Ubuntu host as root. Idempotent.
#
# Order on the server: fleet_base → lian_node → fleet_agent (per servers.ncl).
# TLS material is delivered by fleet-agent install_tls after the agent connects
# (adr-009: cert-manager source, no acme_certd dependency).
#
# Inputs (env file, rendered by Tera from workspace NCL):
# LIAN_NODE_LOGICAL_ID — fleet logical id (rejected if "UNSET")
# LIAN_NODE_PRIVATE_IP — pinned private IP (informational)
# BUILDKIT_VERSION — moby/buildkit release tag (e.g. v0.29.0)
# SCCACHE_VERSION — mozilla/sccache release tag (e.g. v0.9.1)
# NUSHELL_VERSION — nushell release tag (e.g. 0.112.2)
# JUST_VERSION — casey/just release version (e.g. 1.51.0)
# ORAS_VERSION — oras-project/oras release version (e.g. 1.3.2)
# BUILDKIT_ROOT — buildkit data dir
# SCCACHE_DISK_PATH — local sccache cache dir
# BUILDKIT_TLS_DOMAIN — FQDN the cert covers (e.g. fleet.librecloud.online)
# BUILDKIT_TLS_CERT_PATH — leaf cert path (default /etc/buildkit/tls/server.crt)
# BUILDKIT_TLS_CA_PATH — CA cert path for mTLS (default /etc/buildkit/tls/ca.crt)
# BUILDKIT_TLS_KEY_PATH — private key path (default /etc/buildkit/tls/server.key)
# BUILDKIT_TCP_LISTEN_PORT — TLS TCP listen port (default 1234)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
for f in env-lian_node _credentials.env; do
if [ -f "${SCRIPT_DIR}/${f}" ]; then
# shellcheck source=/dev/null
source "${SCRIPT_DIR}/${f}"
fi
done
: "${LIAN_NODE_LOGICAL_ID:?LIAN_NODE_LOGICAL_ID must be set}"
: "${BUILDKIT_VERSION:?BUILDKIT_VERSION must be set}"
: "${SCCACHE_VERSION:?SCCACHE_VERSION must be set}"
: "${NUSHELL_VERSION:?NUSHELL_VERSION must be set}"
: "${JUST_VERSION:?JUST_VERSION must be set}"
: "${ORAS_VERSION:?ORAS_VERSION must be set}"
: "${BUILDKIT_TLS_DOMAIN:?BUILDKIT_TLS_DOMAIN must be set}"
BUILDKIT_TLS_CERT_PATH="${BUILDKIT_TLS_CERT_PATH:-/etc/buildkit/tls/server.crt}"
BUILDKIT_TLS_CA_PATH="${BUILDKIT_TLS_CA_PATH:-/etc/buildkit/tls/ca.crt}"
BUILDKIT_TLS_KEY_PATH="${BUILDKIT_TLS_KEY_PATH:-/etc/buildkit/tls/server.key}"
BUILDKIT_ROOT="${BUILDKIT_ROOT:-/var/lib/buildkit}"
SCCACHE_DISK_PATH="${SCCACHE_DISK_PATH:-/var/cache/sccache}"
BUILDKIT_TCP_LISTEN_PORT="${BUILDKIT_TCP_LISTEN_PORT:-1234}"
# Swap policy (see NCL `swap` block in lian_node component contract).
SWAP_MODE="${SWAP_MODE:-preserve}"
SWAP_SIZE_GB="${SWAP_SIZE_GB:-8}"
SWAP_PATH="${SWAP_PATH:-/swapfile}"
SWAP_SWAPPINESS="${SWAP_SWAPPINESS:-10}"
SWAP_VFS_CACHE_PRESSURE="${SWAP_VFS_CACHE_PRESSURE:-50}"
bold() { printf "\033[1m%s\033[0m\n" "$*"; }
fatal() { printf "\033[31mFATAL:\033[0m %s\n" "$*" >&2; exit 1; }
# ── Swap management ──────────────────────────────────────────────────
# Three modes, declared per-node in NCL:
# 'file — create $SWAP_PATH, mkswap, swapon, persist via /etc/fstab
# 'disabled — swapoff -a, remove /etc/fstab entries (cluster-worker prep)
# 'preserve — no-op (default — leave whatever host state exists)
# Idempotent: re-runs converge to the declared state, never duplicate fstab.
_setup_swap_file() {
local size_gb="$1" path="$2"
# If existing file has wrong size, recreate.
if [ -f "$path" ]; then
local actual_bytes
actual_bytes=$(stat -c %s "$path" 2>/dev/null || stat -f %z "$path" 2>/dev/null || echo 0)
local want_bytes=$((size_gb * 1024 * 1024 * 1024))
if [ "$actual_bytes" -eq "$want_bytes" ] && swapon --show=NAME --noheadings 2>/dev/null | grep -qx "$path"; then
echo " swap: $path already active at ${size_gb}G — no-op"
return 0
fi
echo " swap: recreating $path (was ${actual_bytes}B, want ${want_bytes}B or not active)"
swapoff "$path" 2>/dev/null || true
rm -f "$path"
fi
# Prefer fallocate (instant, no zero-fill); fall back to dd if fs lacks support.
if ! fallocate -l "${size_gb}G" "$path" 2>/dev/null; then
echo " swap: fallocate failed (xfs/ext4 without extent allocation?) — falling back to dd"
dd if=/dev/zero of="$path" bs=1M count=$((size_gb * 1024)) status=progress
fi
chmod 0600 "$path"
mkswap "$path" >/dev/null
swapon "$path"
# Persist via /etc/fstab. Idempotent.
if ! grep -q "^${path}\b" /etc/fstab 2>/dev/null; then
printf '%s\tnone\tswap\tsw\t0 0\n' "$path" >> /etc/fstab
fi
echo " swap: ${path} active at ${size_gb}G + persisted in /etc/fstab"
}
_setup_swap_sysctl() {
local swappiness="$1" cache_pressure="$2"
local conf="/etc/sysctl.d/99-lian-node-swap.conf"
cat > "${conf}.new" <<EOF
# Auto-generated by install-lian_node.sh — swap tuning for build workload.
vm.swappiness=${swappiness}
vm.vfs_cache_pressure=${cache_pressure}
EOF
chmod 0644 "${conf}.new"
mv "${conf}.new" "$conf"
sysctl -p "$conf" >/dev/null
echo " swap: vm.swappiness=${swappiness} vm.vfs_cache_pressure=${cache_pressure} applied"
}
_disable_swap() {
swapoff -a 2>/dev/null || true
if grep -qE '^[^#].*\bswap\b' /etc/fstab 2>/dev/null; then
# Comment out any swap line (don't delete — keeps history for postmortem).
sed -i.bak '/[[:space:]]swap[[:space:]]/s/^/# disabled-by-lian_node /' /etc/fstab
echo " swap: disabled + /etc/fstab swap entries commented out"
else
echo " swap: already off"
fi
}
apply_swap_policy() {
bold "==> swap policy: ${SWAP_MODE}"
case "$SWAP_MODE" in
file)
_setup_swap_file "$SWAP_SIZE_GB" "$SWAP_PATH"
_setup_swap_sysctl "$SWAP_SWAPPINESS" "$SWAP_VFS_CACHE_PRESSURE"
;;
disabled)
_disable_swap
;;
preserve)
echo " swap: preserve — no changes to host state"
;;
*)
fatal "unknown SWAP_MODE='$SWAP_MODE' (expected file|disabled|preserve)"
;;
esac
}
[ "$(id -u)" -eq 0 ] || fatal "must run as root"
[ "$LIAN_NODE_LOGICAL_ID" != "UNSET" ] || fatal "LIAN_NODE_LOGICAL_ID is sentinel 'UNSET' — caller must override"
ARCH=$(dpkg --print-architecture)
case "$ARCH" in
arm64|amd64) ;;
*) fatal "unsupported arch: $ARCH" ;;
esac
# arch translations
RUST_TRIPLE_ARM64="aarch64-unknown-linux-musl"
RUST_TRIPLE_AMD64="x86_64-unknown-linux-musl"
NU_TRIPLE_ARM64="aarch64-unknown-linux-gnu"
NU_TRIPLE_AMD64="x86_64-unknown-linux-gnu"
ORAS_ARCH="$( [ "$ARCH" = "arm64" ] && echo arm64 || echo amd64 )"
ACTION="${1:-install}"
# Download + install a single binary from a github tarball.
# Usage: _fetch_bin <url> <member-in-tar> <dest-path>
_fetch_bin() {
local url="$1" member="$2" dest="$3"
local tmp; tmp=$(mktemp -d)
wget -q -O "${tmp}/pkg.tgz" "${url}"
tar -xzf "${tmp}/pkg.tgz" -C "${tmp}"
if [ ! -f "${tmp}/${member}" ]; then
# Some tarballs nest under <archive>/; find it.
local found
found=$(find "${tmp}" -name "$(basename "${member}")" -type f | head -1 || true)
[ -n "${found}" ] || { rm -rf "${tmp}"; fatal "binary ${member} not found in ${url}"; }
install -m 0755 "${found}" "${dest}"
else
install -m 0755 "${tmp}/${member}" "${dest}"
fi
rm -rf "${tmp}"
}
cmd_install() {
# Swap policy first — buildkit + linker workloads benefit from the overflow
# buffer being in place BEFORE any heavy install (cargo, oras pulls).
apply_swap_policy
bold "==> [1/8] Ensure system packages"
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq \
uidmap fuse-overlayfs slirp4netns \
wget curl ca-certificates
bold "==> [2/8] Install just ${JUST_VERSION}"
if just --version 2>/dev/null | grep -q "${JUST_VERSION}"; then
echo " just ${JUST_VERSION} already installed"
else
local triple="${RUST_TRIPLE_ARM64}"
[ "$ARCH" = "amd64" ] && triple="${RUST_TRIPLE_AMD64}"
_fetch_bin "https://github.com/casey/just/releases/download/${JUST_VERSION}/just-${JUST_VERSION}-${triple}.tar.gz" "just" /usr/local/bin/just
echo " installed $(just --version)"
fi
bold "==> [3/8] Install oras ${ORAS_VERSION}"
if oras version 2>/dev/null | grep -q "${ORAS_VERSION}"; then
echo " oras ${ORAS_VERSION} already installed"
else
_fetch_bin "https://github.com/oras-project/oras/releases/download/v${ORAS_VERSION}/oras_${ORAS_VERSION}_linux_${ORAS_ARCH}.tar.gz" "oras" /usr/local/bin/oras
echo " installed $(oras version | head -1)"
fi
bold "==> [4/8] Install buildkit ${BUILDKIT_VERSION}"
if buildctl --version 2>/dev/null | grep -q "${BUILDKIT_VERSION#v}" && [ -x /usr/local/bin/buildkit-runc ]; then
echo " buildkit ${BUILDKIT_VERSION} already installed (with worker)"
else
local tmp; tmp=$(mktemp -d)
wget -q -O "${tmp}/buildkit.tar.gz" \
"https://github.com/moby/buildkit/releases/download/${BUILDKIT_VERSION}/buildkit-${BUILDKIT_VERSION}.linux-${ARCH}.tar.gz"
# Extract ALL binaries (buildkitd, buildctl, buildkit-runc, buildkit-cni-*, qemu-*).
# buildkit-runc REQUIRED — without it buildkitd fails with "no worker found".
tar -xzf "${tmp}/buildkit.tar.gz" -C /usr/local/
rm -rf "${tmp}"
echo " installed $(buildctl --version)"
fi
bold "==> [5/8] Install sccache ${SCCACHE_VERSION}"
if sccache --version 2>/dev/null | grep -q "${SCCACHE_VERSION#v}"; then
echo " sccache ${SCCACHE_VERSION} already installed"
else
local triple="${RUST_TRIPLE_ARM64}"
[ "$ARCH" = "amd64" ] && triple="${RUST_TRIPLE_AMD64}"
local member="sccache-${SCCACHE_VERSION}-${triple}/sccache"
_fetch_bin "https://github.com/mozilla/sccache/releases/download/${SCCACHE_VERSION}/sccache-${SCCACHE_VERSION}-${triple}.tar.gz" "${member}" /usr/local/bin/sccache
echo " installed $(sccache --version)"
fi
bold "==> [6/8] Install nushell ${NUSHELL_VERSION}"
if nu --version 2>/dev/null | grep -q "${NUSHELL_VERSION}"; then
echo " nushell ${NUSHELL_VERSION} already installed"
else
local triple="${NU_TRIPLE_ARM64}"
[ "$ARCH" = "amd64" ] && triple="${NU_TRIPLE_AMD64}"
local member="nu-${NUSHELL_VERSION}-${triple}/nu"
_fetch_bin "https://github.com/nushell/nushell/releases/download/${NUSHELL_VERSION}/nu-${NUSHELL_VERSION}-${triple}.tar.gz" "${member}" /usr/local/bin/nu
echo " installed $(nu --version)"
fi
bold "==> [7/8] Configure buildkitd systemd unit with TLS"
install -d -m 0755 "${BUILDKIT_ROOT}" "${SCCACHE_DISK_PATH}"
# Pre-create the TLS dir fleet-agent's install_tls will write into.
install -d -m 0750 /etc/buildkit/tls
groupadd --system buildkit 2>/dev/null || true
chown root:buildkit /etc/buildkit/tls
cat > /etc/systemd/system/buildkitd.service <<EOF
[Unit]
Description=BuildKit daemon — persistent lian_node (${LIAN_NODE_LOGICAL_ID})
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
ExecStart=/usr/local/bin/buildkitd \\
--addr unix:///run/buildkit/buildkitd.sock \\
--addr tcp://0.0.0.0:${BUILDKIT_TCP_LISTEN_PORT} \\
--tlscacert ${BUILDKIT_TLS_CA_PATH} \\
--tlscert ${BUILDKIT_TLS_CERT_PATH} \\
--tlskey ${BUILDKIT_TLS_KEY_PATH} \\
--root ${BUILDKIT_ROOT}
Restart=on-failure
RestartSec=5s
RuntimeDirectory=buildkit
RuntimeDirectoryMode=0711
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable buildkitd
# Certs are absent until fleet-agent delivers install_tls — tolerate first-run failure.
systemctl restart buildkitd 2>/dev/null || echo " buildkitd: start deferred (certs pending install_tls from fleet-agent)"
bold "==> [8/9] Fleet operational wrappers"
# Docker-compat credential bridge: oras and other OCI tools read from
# $DOCKER_CONFIG/config.json. fleet_base writes /etc/containers/auth.json;
# expose it under /etc/fleet/docker/config.json so DOCKER_CONFIG works.
install -d -m 0755 /etc/fleet/docker
ln -sfn /etc/containers/auth.json /etc/fleet/docker/config.json
cat > /usr/local/bin/fleet-oras <<'WRAPPER'
#!/bin/sh
DOCKER_CONFIG=/etc/fleet/docker exec oras "$@"
WRAPPER
chmod 0755 /usr/local/bin/fleet-oras
cat > /usr/local/bin/fleet-buildctl <<'WRAPPER'
#!/bin/sh
exec buildctl --addr unix:///run/buildkit/buildkitd.sock "$@"
WRAPPER
chmod 0755 /usr/local/bin/fleet-buildctl
echo " fleet-oras : DOCKER_CONFIG=/etc/fleet/docker → /etc/containers/auth.json"
echo " fleet-buildctl : addr=unix:///run/buildkit/buildkitd.sock"
bold "==> [9/9] sshd hardening"
install -d -m 0755 /etc/ssh/sshd_config.d
cat > /etc/ssh/sshd_config.d/10-lian-node-hardening.conf <<'EOF'
PasswordAuthentication no
PubkeyAuthentication yes
PermitRootLogin prohibit-password
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
EOF
systemctl reload ssh || systemctl reload sshd || true
bold "==> Done"
systemctl is-active buildkitd >/dev/null && echo " buildkitd : active (tls on :${BUILDKIT_TCP_LISTEN_PORT})" || echo " buildkitd : pending TLS (install_tls from fleet-agent will start it)"
echo " logical_id : ${LIAN_NODE_LOGICAL_ID}"
echo " private_ip : ${LIAN_NODE_PRIVATE_IP}"
echo " tls domain : ${BUILDKIT_TLS_DOMAIN}"
echo " buildctl : $(buildctl --version)"
echo " just : $(just --version)"
echo " oras : $(oras version | head -1)"
echo " sccache : $(sccache --version)"
echo " nushell : $(nu --version)"
}
cmd_status() {
systemctl status buildkitd --no-pager
echo "=== wrappers ==="
echo " fleet-oras : $([ -x /usr/local/bin/fleet-oras ] && echo ok || echo MISSING)"
echo " fleet-buildctl : $([ -x /usr/local/bin/fleet-buildctl ] && echo ok || echo MISSING)"
echo " docker config : $([ -L /etc/fleet/docker/config.json ] && readlink /etc/fleet/docker/config.json || echo MISSING)"
echo "=== swap ==="
local active
active=$(swapon --show=NAME,SIZE,USED --noheadings 2>/dev/null || true)
if [ -n "$active" ]; then
printf ' %s\n' "$active"
else
echo " (no swap active)"
fi
echo " swappiness : $(sysctl -n vm.swappiness 2>/dev/null || echo '?')"
echo " vfs_cache_pressure : $(sysctl -n vm.vfs_cache_pressure 2>/dev/null || echo '?')"
echo " fstab entries : $(grep -E '[[:space:]]swap[[:space:]]' /etc/fstab 2>/dev/null | grep -v '^#' | wc -l | tr -d ' ')"
}
cmd_uninstall() {
systemctl disable --now buildkitd 2>/dev/null || true
rm -f /etc/systemd/system/buildkitd.service
systemctl daemon-reload
rm -f /usr/local/bin/buildkitd /usr/local/bin/buildctl /usr/local/bin/buildkit-runc
rm -f /usr/local/bin/just /usr/local/bin/oras
rm -f /usr/local/bin/fleet-oras /usr/local/bin/fleet-buildctl
rm -f /etc/fleet/docker/config.json
rmdir --ignore-fail-on-non-empty /etc/fleet/docker 2>/dev/null || true
rm -f /etc/ssh/sshd_config.d/10-lian-node-hardening.conf
rm -f /etc/acme-certd/reload.d/buildkitd.sh
echo "uninstalled (data dirs ${BUILDKIT_ROOT} and ${SCCACHE_DISK_PATH} preserved)"
}
case "$ACTION" in
install) cmd_install ;;
status) cmd_status ;;
uninstall) cmd_uninstall ;;
*) fatal "unknown action: $ACTION (install|status|uninstall)" ;;
esac