disinto/lib/init/nomad/install.sh
Claude 06ead3a19d
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pr/secret-scan Pipeline was successful
fix: [nomad-step-0] S0.2 — install nomad + systemd unit + nomad/server.hcl/client.hcl (#822)
Lands the Nomad install + baseline HCL config for the single-node factory
dev box. Nothing is wired into `disinto init` yet — S0.4 does that.

- lib/init/nomad/install.sh: idempotent apt install pinned to
  NOMAD_VERSION (default 1.9.5). Adds HashiCorp apt keyring and sources
  list only if absent; fast-paths when the pinned version is already
  installed.
- lib/init/nomad/systemd-nomad.sh: writes /etc/systemd/system/nomad.service
  (rewrites only when content differs), creates /etc/nomad.d and
  /var/lib/nomad, runs `systemctl enable nomad` WITHOUT starting.
- nomad/server.hcl: single-node combined server+client role. bootstrap_expect=1,
  localhost bind, default ports pinned explicitly, UI enabled. No TLS/ACL —
  factory dev box baseline.
- nomad/client.hcl: Docker task driver (allow_privileged=false, volumes
  enabled) and host_volume pre-wiring for forgejo-data, woodpecker-data,
  agent-data, project-repos, caddy-data, chat-history, ops-repo under
  /srv/disinto/*.

Verified: `nomad config validate nomad/*.hcl` reports "Configuration is
valid!" (with expected TLS/bootstrap warnings for a dev box). Shellcheck
clean across the repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 06:04:02 +00:00

118 lines
5.1 KiB
Bash
Executable file

#!/usr/bin/env bash
# =============================================================================
# lib/init/nomad/install.sh — Idempotent apt install of HashiCorp Nomad
#
# Part of the Nomad+Vault migration (S0.2, issue #822). Installs the `nomad`
# binary from the HashiCorp apt repository. Does NOT install Vault — S0.3
# owns that. Does NOT configure, start, or enable a systemd unit —
# lib/init/nomad/systemd-nomad.sh owns that. Does NOT wire this script into
# `disinto init` — S0.4 owns that.
#
# Idempotency contract:
# - Running twice back-to-back is a no-op once the target version is
# installed and the apt source is in place.
# - Adds the HashiCorp apt keyring only if it is absent.
# - Adds the HashiCorp apt sources list only if it is absent.
# - Skips `apt-get install` entirely when the installed version already
# matches ${NOMAD_VERSION}.
#
# Configuration:
# NOMAD_VERSION — pinned Nomad version (default: see below). The apt
# package name is versioned as "nomad=<version>-1".
#
# Usage:
# sudo NOMAD_VERSION=1.9.5 lib/init/nomad/install.sh
#
# Exit codes:
# 0 success (installed or already present)
# 1 precondition failure (not Debian/Ubuntu, missing tools, not root)
# =============================================================================
set -euo pipefail
# Pin to a specific Nomad 1.x release. Bump here, not at call sites.
NOMAD_VERSION="${NOMAD_VERSION:-1.9.5}"
HASHICORP_KEYRING="/usr/share/keyrings/hashicorp-archive-keyring.gpg"
HASHICORP_SOURCES="/etc/apt/sources.list.d/hashicorp.list"
HASHICORP_GPG_URL="https://apt.releases.hashicorp.com/gpg"
HASHICORP_REPO_URL="https://apt.releases.hashicorp.com"
log() { printf '[install-nomad] %s\n' "$*"; }
die() { printf '[install-nomad] ERROR: %s\n' "$*" >&2; exit 1; }
# ── Preconditions ────────────────────────────────────────────────────────────
if [ "$(id -u)" -ne 0 ]; then
die "must run as root (needs apt-get + /usr/share/keyrings write access)"
fi
for bin in apt-get gpg curl lsb_release; do
command -v "$bin" >/dev/null 2>&1 \
|| die "required binary not found: ${bin}"
done
CODENAME="$(lsb_release -cs)"
[ -n "$CODENAME" ] || die "lsb_release returned empty codename"
# ── Fast-path: already at desired version? ───────────────────────────────────
installed_version=""
if command -v nomad >/dev/null 2>&1; then
# `nomad version` prints e.g. "Nomad v1.9.5" on the first line.
installed_version="$(nomad version 2>/dev/null \
| awk 'NR==1 {sub(/^v/, "", $2); print $2; exit}')"
fi
if [ "$installed_version" = "$NOMAD_VERSION" ]; then
log "nomad ${NOMAD_VERSION} already installed — nothing to do"
exit 0
fi
# ── Ensure HashiCorp apt keyring ─────────────────────────────────────────────
if [ ! -f "$HASHICORP_KEYRING" ]; then
log "adding HashiCorp apt keyring → ${HASHICORP_KEYRING}"
tmpkey="$(mktemp)"
trap 'rm -f "$tmpkey"' EXIT
curl -fsSL "$HASHICORP_GPG_URL" -o "$tmpkey" \
|| die "failed to fetch HashiCorp GPG key from ${HASHICORP_GPG_URL}"
gpg --dearmor -o "$HASHICORP_KEYRING" < "$tmpkey" \
|| die "failed to dearmor HashiCorp GPG key"
chmod 0644 "$HASHICORP_KEYRING"
rm -f "$tmpkey"
trap - EXIT
else
log "HashiCorp apt keyring already present"
fi
# ── Ensure HashiCorp apt sources list ────────────────────────────────────────
desired_source="deb [signed-by=${HASHICORP_KEYRING}] ${HASHICORP_REPO_URL} ${CODENAME} main"
if [ ! -f "$HASHICORP_SOURCES" ] \
|| ! grep -qxF "$desired_source" "$HASHICORP_SOURCES"; then
log "writing HashiCorp apt sources list → ${HASHICORP_SOURCES}"
printf '%s\n' "$desired_source" > "$HASHICORP_SOURCES"
apt_update_needed=1
else
log "HashiCorp apt sources list already present"
apt_update_needed=0
fi
# ── Install the pinned version ───────────────────────────────────────────────
if [ "$apt_update_needed" -eq 1 ]; then
log "running apt-get update"
DEBIAN_FRONTEND=noninteractive apt-get update -qq \
|| die "apt-get update failed"
fi
# HashiCorp apt packages use the "<version>-1" package-revision suffix.
pkg_spec="nomad=${NOMAD_VERSION}-1"
log "installing ${pkg_spec}"
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
"$pkg_spec" \
|| die "apt-get install ${pkg_spec} failed"
# ── Verify ───────────────────────────────────────────────────────────────────
final_version="$(nomad version 2>/dev/null \
| awk 'NR==1 {sub(/^v/, "", $2); print $2; exit}')"
if [ "$final_version" != "$NOMAD_VERSION" ]; then
die "post-install check: expected ${NOMAD_VERSION}, got '${final_version}'"
fi
log "nomad ${NOMAD_VERSION} installed successfully"