#!/usr/bin/env bats # Seam: frank-primary — the Dankserver LLM endpoint on :7770. # # This file asserts the LUCEBOX shape (the 2026-09-26 cutover that replaced # frank-medium): a rootless Quadlet running the lucebox DS4.1 engine on the # two R9700s, with the model mmapped out of /home/dank/models and the # entrypoint/healthcheck mounted straight out of this checkout. load helpers UNIT="$REPO_ROOT/dankserver/frank-primary/quadlet/frank-primary.container" EP="$REPO_ROOT/dankserver/frank-primary/frank-primary-entrypoint.sh" HC="$REPO_ROOT/dankserver/frank-primary/frank-primary-healthcheck.py" @test "the frank-primary quadlet file and its mounted scripts exist" { [ -f "$UNIT" ] [ -x "$EP" ] [ -f "$HC" ] # The healthcheck is python; a syntax error would leave the container # permanently unhealthy with the failure hidden inside HealthCmd. python3 -m py_compile "$HC" } @test "frank-primary is boot-persistent and self-healing" { # Rootless Podman does not auto-start bare containers and restart: # unless-stopped does not survive shutdown's explicit stop. The unit must # carry both the boot symlink and a restart policy (2026-08-26 incident: # a compose-only Caddy never came back after a reboot). grep -q '^WantedBy=default.target' "$UNIT" grep -qE '^Restart=(always|on-failure)' "$UNIT" } @test "frank-primary binds the host network and needs no published port" { # luce_server binds 0.0.0.0:7770 directly, so the container takes the # host netns. A PublishPort would be dead config. grep -q -- '--network=host' "$UNIT" ! grep -q '^PublishPort=' "$UNIT" } @test "frank-primary reaches both R9700s and hides the Raphael iGPU" { # /dev/kfd + /dev/dri via AddDevice; HIP_VISIBLE_DEVICES=0,1 keeps the # host's Raphael iGPU (index 2) out of the enumeration so the engine's # device picks cannot land on it. grep -q '^AddDevice=/dev/kfd' "$UNIT" grep -q '^AddDevice=/dev/dri' "$UNIT" grep -q '^Environment=HIP_VISIBLE_DEVICES=0,1$' "$UNIT" } @test "frank-primary never names a bare HIP-reserved env var" { # The HIP runtime reads bare TEMP for its JIT cache path (the 2026-08-13 # incident: TEMP=1.0 sent the JIT cache to path "1.0" and segfaulted every # load). HOST and GPU_MAX_HEAP_SIZE are the same class. The unit's # Environment= lines must not define them; the entrypoint exports # GPU_MAX_HW_QUEUES (explicitly not GPU_MAX_HEAP_SIZE). ! grep -qE '^Environment=(TEMP|HOST|GPU_MAX_HEAP_SIZE)=' "$UNIT" ! grep -qE '^(export )?(TEMP|HOST|GPU_MAX_HEAP_SIZE)=' "$EP" } @test "the unit mounts the entrypoint and healthcheck from the checkout" { # deploy.sh git-pulls this checkout before dispatching, so a Volume # reference is how an edit here reaches the container without an image # rebuild. Absolute host paths, read-only. grep -q 'Volume=/home/dank/serverAppConfigs/dankserver/frank-primary/frank-primary-entrypoint.sh:/usr/local/bin/frank-primary-entrypoint.sh:ro' "$UNIT" grep -q 'Volume=/home/dank/serverAppConfigs/dankserver/frank-primary/frank-primary-healthcheck.py:/usr/local/bin/frank-primary-healthcheck.py:ro' "$UNIT" } @test "first-start patience is configured on both sides of the health check" { # Cold start page-ins the dense weights and builds the graphs; the # container must not be killed as unhealthy while that happens, and the # deployer must wait the same order of magnitude. grep -q '^HealthStartPeriod=1800s' "$UNIT" grep -q '^TimeoutStartSec=1800$' "$UNIT" grep -q 'wait_container_healthy frank-primary 1800' \ "$REPO_ROOT/scripts/deploy-remote.sh" } @test "the deployer refuses to start without image and complete payload" { # A 377 GiB partial download must never be served: the handler gates on # the image being present and the model file existing at its exact # declared byte count (stat -c %s, never wc -c — wc read the whole stream # for minutes on 2026-09-26). grep -q 'deploy_llm_primary() {' "$REPO_ROOT/scripts/deploy-remote.sh" grep -q 'podman image exists localhost/lucebox-rocm:gfx1201' \ "$REPO_ROOT/scripts/deploy-remote.sh" grep -q 'want_size=405349859328' "$REPO_ROOT/scripts/deploy-remote.sh" grep -q 'stat -c %s' "$REPO_ROOT/scripts/deploy-remote.sh" } @test "the deployer gates on the port actually being free" { # 7770 is a host-network bind: the previous holder must release it before # the new container can be created, or pasta's bind fails at create time # and the failure hides behind Restart=. grep -q 'wait_port_free 7770' "$REPO_ROOT/scripts/deploy-remote.sh" }