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
|
|
|
# =============================================================================
|
|
|
|
|
# nomad/server.hcl — Single-node combined server+client configuration
|
|
|
|
|
#
|
|
|
|
|
# Part of the Nomad+Vault migration (S0.2, issue #822). Deployed to
|
|
|
|
|
# /etc/nomad.d/server.hcl on the factory dev box alongside client.hcl.
|
|
|
|
|
#
|
|
|
|
|
# This file owns: agent role, ports, bind, data directory.
|
|
|
|
|
# client.hcl owns: Docker driver plugin config + host_volume declarations.
|
|
|
|
|
#
|
|
|
|
|
# NOTE: On single-node setups these two files could be merged into one
|
|
|
|
|
# (Nomad auto-merges every *.hcl under -config=/etc/nomad.d). The split is
|
|
|
|
|
# purely for readability — role/bind/port vs. plugin/volume wiring.
|
|
|
|
|
#
|
|
|
|
|
# This is a factory dev-box baseline — TLS, ACLs, gossip encryption, and
|
|
|
|
|
# consul/vault integration are deliberately absent and land in later steps.
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
data_dir = "/var/lib/nomad"
|
|
|
|
|
bind_addr = "127.0.0.1"
|
|
|
|
|
log_level = "INFO"
|
|
|
|
|
|
|
|
|
|
# All Nomad agent traffic stays on localhost — the factory box does not
|
|
|
|
|
# federate with peers. Ports are the Nomad defaults, pinned here so that
|
|
|
|
|
# future changes to these numbers are a visible diff.
|
|
|
|
|
ports {
|
|
|
|
|
http = 4646
|
|
|
|
|
rpc = 4647
|
|
|
|
|
serf = 4648
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Single-node combined mode: this agent is both the only server and the
|
|
|
|
|
# only client. bootstrap_expect=1 makes the server quorum-of-one.
|
|
|
|
|
server {
|
|
|
|
|
enabled = true
|
|
|
|
|
bootstrap_expect = 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
client {
|
|
|
|
|
enabled = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Advertise localhost to self to avoid surprises if the default IP
|
|
|
|
|
# autodetection picks a transient interface (e.g. docker0, wg0).
|
|
|
|
|
advertise {
|
|
|
|
|
http = "127.0.0.1"
|
|
|
|
|
rpc = "127.0.0.1"
|
|
|
|
|
serf = "127.0.0.1"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# UI on by default — same bind as http, no TLS (localhost only).
|
|
|
|
|
ui {
|
|
|
|
|
enabled = true
|
|
|
|
|
}
|
fix: [nomad-step-2] S2.3 — vault-nomad-auth.sh (enable JWT auth + roles + nomad workload identity) (#881)
Wires Nomad → Vault via workload identity so jobs can exchange their
short-lived JWT for a Vault token carrying the policies in
vault/policies/ — no shared VAULT_TOKEN in job env.
- `lib/init/nomad/vault-nomad-auth.sh` — idempotent script: enable jwt
auth at path `jwt-nomad`, config JWKS/algs, apply roles, install
server.hcl + SIGHUP nomad on change.
- `tools/vault-apply-roles.sh` — companion sync script (S2.1 sibling);
reads vault/roles.yaml and upserts each Vault role under
auth/jwt-nomad/role/<name> with created/updated/unchanged semantics.
- `vault/roles.yaml` — declarative role→policy→bound_claims map; one
entry per vault/policies/*.hcl. Keeps S2.1 policies and S2.3 role
bindings visible side-by-side at review time.
- `nomad/server.hcl` — adds vault stanza (enabled, address,
default_identity.aud=["vault.io"], ttl=1h).
- `lib/hvault.sh` — new `hvault_get_or_empty` helper shared between
vault-apply-policies.sh, vault-apply-roles.sh, and vault-nomad-auth.sh;
reads a Vault endpoint and distinguishes 200 / 404 / other.
- `vault/policies/AGENTS.md` — extends S2.1 docs with JWT-auth role
naming convention, token shape, and the "add new service" flow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 16:44:22 +00:00
|
|
|
|
|
|
|
|
# ─── Vault integration (S2.3, issue #881) ───────────────────────────────────
|
|
|
|
|
# Nomad jobs exchange their short-lived workload-identity JWT (signed by
|
|
|
|
|
# nomad's built-in signer at /.well-known/jwks.json on :4646) for a Vault
|
|
|
|
|
# token carrying the policies named by the role in `vault { role = "..." }`
|
|
|
|
|
# of each jobspec — no shared VAULT_TOKEN in job env.
|
|
|
|
|
#
|
|
|
|
|
# The JWT auth path (jwt-nomad) + per-role bindings live on the Vault
|
|
|
|
|
# side, written by lib/init/nomad/vault-nomad-auth.sh + tools/vault-apply-roles.sh.
|
|
|
|
|
# Roles are defined in vault/roles.yaml.
|
|
|
|
|
#
|
|
|
|
|
# `default_identity.aud = ["vault.io"]` matches bound_audiences on every
|
|
|
|
|
# role in vault/roles.yaml — a drift here would silently break every job's
|
|
|
|
|
# Vault token exchange at placement time.
|
|
|
|
|
vault {
|
|
|
|
|
enabled = true
|
|
|
|
|
address = "http://127.0.0.1:8200"
|
|
|
|
|
|
|
|
|
|
default_identity {
|
|
|
|
|
aud = ["vault.io"]
|
|
|
|
|
ttl = "1h"
|
|
|
|
|
}
|
|
|
|
|
}
|