5 Commits
Author SHA1 Message Date
l.kirchner abc62f5704 Merge pull request 'feat(nexus): runtime provisioning for the decided stack (nexus-hub K-101)' (#3) from k101/nexus-runtime into main 2026-06-11 15:18:14 +02:00
l.kirchner ad36417b30 Merge branch 'fix/nexus-service-condition' 2026-06-11 14:54:47 +02:00
l.kirchner afd0a4ea35 fix(nexus): guard nexus.service with ConditionPathExists
A reboot before the first deploy would leave the enabled unit in failed
state; the condition keeps it inert until start.sh exists (same guard as
nexus-worker.service).
2026-06-11 14:54:47 +02:00
l.kirchner 2152683397 Merge branch 'feat/nexus-runtime-k101' 2026-06-11 14:48:53 +02:00
l.kirchner 1dd89e25bc feat(nexus): runtime provisioning for the decided stack (nexus-hub K-101)
- new install/nexus-runtime.sh (idempotent, re-runnable on an existing
  LXC): uv for the nexus user (manages Python 3.12), tesseract deu+eng,
  nexus-worker.service unit (ConditionPathExists guards the skeleton
  phase), sudoers extended to cover the worker service
- nexus-install.sh: RUNTIME section now invokes nexus-runtime.sh at the
  end of the install (after base sudoers/units, which it extends)
2026-06-11 14:48:53 +02:00
2 changed files with 102 additions and 5 deletions
+15 -5
View File
@@ -57,11 +57,12 @@ else
msg_warn "Node $(node -v) already present, skipping" msg_warn "Node $(node -v) already present, skipping"
fi fi
# ── RUNTIME (extended by the stack-introducing SDD card in nexus-hub) ───────── # ── RUNTIME ───────────────────────────────────────────────────────────────────
# The nexus tech stack is not yet decided (nexus-hub Project Brief, open point 2). # Stack decided (nexus-hub ADR-0002, card K-101): Python 3.12 via uv, tesseract
# When the stack card lands, it adds the runtime here (e.g. Python/uv, DB client # deu+eng, nexus-worker.service + sudoers extension. Provisioned by
# libs) AND documents the change in nexus-hub docs/agent-rules.md → Projekt-Kommandos. # install/nexus-runtime.sh, invoked at the END of this script (it extends the
msg_warn "RUNTIME section is a placeholder until the nexus stack decision (see nexus-hub)" # sudoers rule and systemd units written below, so order matters). Commands are
# documented in nexus-hub docs/05_AGENT_RULES.md → Projekt-Kommandos.
# ── act_runner binary ───────────────────────────────────────────────────────── # ── act_runner binary ─────────────────────────────────────────────────────────
if [[ ! -x /usr/local/bin/act_runner ]]; then if [[ ! -x /usr/local/bin/act_runner ]]; then
@@ -121,6 +122,9 @@ cat >/etc/systemd/system/nexus.service <<EOF
Description=nexus — Family Knowledge Hub Description=nexus — Family Knowledge Hub
After=network-online.target After=network-online.target
Wants=network-online.target Wants=network-online.target
# Skeleton-safe: only startable once the first deploy shipped the artifact
# (otherwise a reboot before first deploy leaves the unit in failed state).
ConditionPathExists=$CURRENT_DIR/start.sh
[Service] [Service]
Type=simple Type=simple
@@ -165,6 +169,12 @@ systemctl enable nexus.service >/dev/null 2>&1
systemctl enable --now nexus-runner.service systemctl enable --now nexus-runner.service
msg_ok "systemd units installed (runner started; nexus.service enabled, starts on first deploy)" msg_ok "systemd units installed (runner started; nexus.service enabled, starts on first deploy)"
# ── RUNTIME provisioning (K-101): uv/Python 3.12, tesseract, worker unit ──────
# Runs LAST on purpose: it extends the sudoers rule and unit set from above.
# Idempotent — the same script retrofits an existing LXC:
# curl -fsSL .../install/nexus-runtime.sh | bash
bash <(curl -fsSL "${LIB_URL%/lib}/install/nexus-runtime.sh")
# ── notes / summary file ────────────────────────────────────────────────────── # ── notes / summary file ──────────────────────────────────────────────────────
CRED_FILE="/root/nexus.credentials" CRED_FILE="/root/nexus.credentials"
cat >"$CRED_FILE" <<EOF cat >"$CRED_FILE" <<EOF
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env bash
# nexus runtime provisioning — idempotent, runs inside the nexus LXC as root.
#
# Added by nexus-hub card K-101 (stack scaffold). Installs everything the
# decided stack (nexus-hub ADR-0002) needs at runtime ON TOP of the base
# nexus-install.sh provisioning:
# - uv for the `nexus` user (manages Python 3.12 per pyproject.toml)
# - tesseract OCR with deu+eng language packs (ingest cards K-107+)
# - nexus-worker.service systemd unit (analysis worker, queue with K-105)
# - sudoers extension so the Actions runner may also restart the worker
#
# Called by install/nexus-install.sh (RUNTIME section) during fresh installs.
# To retrofit an EXISTING LXC (documented K-101 follow-up step), run inside it:
#
# curl -fsSL https://gitea.luki-net.org/luki-net/proxmox-scripts/raw/branch/main/install/nexus-runtime.sh | bash
#
# Safe to re-run at any time.
set -euo pipefail
LIB_URL="${LIB_URL:-https://gitea.luki-net.org/luki-net/proxmox-scripts/raw/branch/main/lib}"
source <(curl -fsSL "$LIB_URL/install.func")
[[ "$EUID" -eq 0 ]] || { msg_err "Must run as root"; exit 1; }
APP_USER="nexus"
APP_HOME="/opt/nexus"
CURRENT_DIR="$APP_HOME/current"
run_user() { runuser -u "$APP_USER" -- env HOME="$APP_HOME" PATH="$APP_HOME/.local/bin:/usr/local/bin:/usr/bin:/bin" "$@"; }
id "$APP_USER" >/dev/null 2>&1 || { msg_err "user $APP_USER missing — run nexus-install.sh first"; exit 1; }
# ── OCR stack (ING-3: tesseract deu+eng) ──────────────────────────────────────
msg_info "Installing tesseract (deu+eng)..."
apt-get install -y -qq tesseract-ocr tesseract-ocr-deu tesseract-ocr-eng >/dev/null
msg_ok "tesseract $(tesseract --version 2>/dev/null | head -n1 | awk '{print $2}')"
# ── uv for the nexus user (provides Python 3.12 via pyproject/uv.lock) ────────
if [[ ! -x "$APP_HOME/.local/bin/uv" ]]; then
msg_info "Installing uv for $APP_USER..."
run_user bash -c "curl -LsSf https://astral.sh/uv/install.sh | sh" >/dev/null
msg_ok "uv $(run_user "$APP_HOME/.local/bin/uv" --version | awk '{print $2}') installed"
else
msg_warn "uv already present ($(run_user "$APP_HOME/.local/bin/uv" --version | awk '{print $2}')), skipping"
fi
run_user "$APP_HOME/.local/bin/uv" python install 3.12 >/dev/null 2>&1 || true
msg_ok "Python 3.12 toolchain available via uv"
# ── nexus-worker.service ──────────────────────────────────────────────────────
cat >/etc/systemd/system/nexus-worker.service <<EOF
[Unit]
Description=nexus — analysis worker (job queue arrives with K-105)
After=network-online.target
Wants=network-online.target
# Skeleton-safe: only start once the deploy artifact provides the entrypoint.
ConditionPathExists=$CURRENT_DIR/start-worker.sh
[Service]
Type=simple
User=$APP_USER
Group=$APP_USER
WorkingDirectory=$CURRENT_DIR
EnvironmentFile=/etc/nexus/env
ExecStart=$CURRENT_DIR/start-worker.sh
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
ProtectSystem=full
PrivateTmp=true
[Install]
WantedBy=multi-user.target
EOF
# ── sudoers: extend the narrow rule to cover the worker service ───────────────
cat >/etc/sudoers.d/nexus-deploy <<EOF
$APP_USER ALL=(root) NOPASSWD: /usr/bin/systemctl restart nexus.service, /usr/bin/systemctl start nexus.service, /usr/bin/systemctl stop nexus.service, /usr/bin/systemctl status nexus.service, /usr/bin/systemctl restart nexus-worker.service, /usr/bin/systemctl start nexus-worker.service, /usr/bin/systemctl stop nexus-worker.service, /usr/bin/systemctl status nexus-worker.service
EOF
chmod 440 /etc/sudoers.d/nexus-deploy
visudo -cf /etc/sudoers.d/nexus-deploy >/dev/null
systemctl daemon-reload
systemctl enable nexus-worker.service >/dev/null 2>&1
msg_ok "nexus-worker.service installed + enabled (starts once a deploy ships start-worker.sh)"
msg_ok "nexus runtime provisioning finished (idempotent — safe to re-run)"