diff --git a/playbooks/irv-ml1-draupnir-engine.yaml b/playbooks/irv-ml1-draupnir-engine.yaml new file mode 100644 index 0000000..ee2b7c7 --- /dev/null +++ b/playbooks/irv-ml1-draupnir-engine.yaml @@ -0,0 +1,77 @@ +# Provision the Draupnir geometry engine on irv-ml1. +# +# scripts/elway irv-ml1 --playbook playbooks/irv-ml1-draupnir-engine.yaml +# +# Records what was VERIFIED on 2026-09-21, not what was planned. +# +# ⚠ SUDO IDENTITY. `ssh irv-ml1` is lkraven and has NO NOPASSWD sudo; +# `ssh infra-ops@irv-ml1` does. The only step needing root is the artifact +# root, so everything else runs as lkraven, where the ssh config and deploy +# keys live. +# +# ⚠ DEPLOY KEY. irv-ml1's ~/.ssh/config pins gitea.phasefinal.com to +# `gitea_brokkr_smithy` with IdentitiesOnly yes — its comment is +# "irv-ml1-r14-deploy", NOT the arbo key that sorts first in ~/.ssh/*.pub. +# Registering the wrong one fails with a message naming the right one, which is +# the clue. The key is registered read-only on pfi/draupnir (id 8). +# +# ⚠ FreeCAD is an AppImage BY CHOICE. The distro package drags a GUI/Qt +# dependency tree onto a box running twelve GPU and audio services; an AppImage +# is self-contained, pinned, and removable with one rm. Run it as +# `FreeCAD.AppImage --appimage-extract-and-run freecadcmd`. +# +# ⚠ SLICER DEFERRED. PrusaSlicer's latest release ships NO Linux AppImage +# (Windows/macOS only), and guessing at a release filename already produced a +# 4 KB error page here once. Left undone deliberately rather than done blind. +vars: + repo: git@gitea.phasefinal.com:pfi/draupnir.git + checkout: /home/lkraven/development/draupnir + freecad_url: https://github.com/FreeCAD/FreeCAD/releases/download/1.0.0/FreeCAD_1.0.0-conda-Linux-x86_64-py311.AppImage + +steps: + - name: Artifact root on smithy (500 GB budget, per draupnir remote.py) + sudo: true + shell: mkdir -p /mnt/smithy/draupnir && chmod 2775 /mnt/smithy/draupnir + creates: /mnt/smithy/draupnir + + - name: Clone the engine repo + shell: git clone {{ repo }} {{ checkout }} + creates: "{{ checkout }}/.git" + + - name: uv venv on the 3.11 floor + shell: cd {{ checkout }} && uv venv --python 3.11 + creates: "{{ checkout }}/.venv/bin/python" + + - name: Install the engine extra (build123d + OCP + numpy + trimesh) + shell: cd {{ checkout }} && uv pip install -e '.[engine]' + # Idempotent on the INSTALLED package, not on the venv existing. + when: "! {{ checkout }}/.venv/bin/python -c 'import build123d' 2>/dev/null" + + - name: FreeCAD headless AppImage + shell: mkdir -p ~/opt && curl -sL -o ~/opt/FreeCAD.AppImage {{ freecad_url }} && chmod +x ~/opt/FreeCAD.AppImage + creates: /home/lkraven/opt/FreeCAD.AppImage + +verify: + - name: build123d, numpy and trimesh all import + shell: "{{ checkout }}/.venv/bin/python -c 'import build123d, numpy, trimesh, OCP'" + changed_when: "false" + - name: FreeCAD runs headless + shell: ~/opt/FreeCAD.AppImage --appimage-extract-and-run freecadcmd --version + changed_when: "false" + - name: artifact root is writable by the docker group + shell: test -w /mnt/smithy/draupnir + changed_when: "false" + # The real gate. Both control parts must BUILD and both volumes must match + # construction-known arithmetic — not a render, not an opinion. + - name: acceptance — contrastive control pair builds with correct volumes + shell: >- + cd {{ checkout }} && .venv/bin/python -c + "import math,runpy; + g=runpy.run_path('parts/plate-good/model.py')['result']; + t=runpy.run_path('parts/plate-thin-wall/model.py')['result']; + wg=40*40*5-math.pi*5**2*5; wt=40*40*5-math.pi*19**2*5; + assert abs(g.volume-wg)<1e-6*wg, (g.volume,wg); + assert abs(t.volume-wt)<1e-6*wt, (t.volume,wt); + assert (round(g.bounding_box().size.X,6),round(g.bounding_box().size.Z,6))==(40.0,5.0); + print('acceptance OK')" + changed_when: "false"