infra: rename truenas-burnin → nas-burnin (1.0.0-41)

Matches the 1.0.0-38 product display rename. Touches every
infrastructure identifier:

- container_name: truenas-burnin → nas-burnin
- forge URL in /api/v1/updates/check
- security-scan: REPO_URL, REPO, DEPLOY_DIR, systemd unit description
- run-tests.sh default container name
- doc paths in README/SPEC/CLAUDE
- in-app instruction strings (login.html, settings.html, auth_cli.py)

Maple migration done in lockstep:
  docker compose down (truenas-burnin)
  mv ~/docker/stacks/{truenas-burnin,nas-burnin}
  systemd unit ExecStart updated + daemon-reload
  docker compose up -d --build → container nas-burnin
  Old image truenas-burnin-app removed (~12 GB reclaimed)
  Stale top-level orphans cleaned (config.py, poller.py, routes.py,
  truenas.py, tests/) — all dead since pre-split refactors

Forge repo rename (git.hellocomputer.xyz/brandon/truenas-burnin →
nas-burnin) is a separate UI-only step. Forgejo redirects the old
URL after rename, so this commit can be pushed to the existing
remote first; remote URL gets updated locally once you rename.
This commit is contained in:
Brandon Walter 2026-05-04 07:16:02 -07:00
parent d38807f957
commit 944e4c1541
13 changed files with 37 additions and 37 deletions

View file

@ -11,8 +11,8 @@ A self-hosted web dashboard for running and tracking hard-drive burn-in tests
against a TrueNAS SCALE 25.10 instance. Deployed on **maple.local** (10.0.0.138). against a TrueNAS SCALE 25.10 instance. Deployed on **maple.local** (10.0.0.138).
- **App URL**: http://10.0.0.138:8084 (or http://burnin.hellocomputer.xyz) - **App URL**: http://10.0.0.138:8084 (or http://burnin.hellocomputer.xyz)
- **Stack path on maple.local**: `~/docker/stacks/truenas-burnin/` - **Stack path on maple.local**: `~/docker/stacks/nas-burnin/`
- **Source (local mac)**: `~/Desktop/claudesandbox/truenas-burnin/` - **Source (local mac)**: `~/Desktop/claudesandbox/nas-burnin/`
- **Compose synced to maple.local** via `scp` or manual copy - **Compose synced to maple.local** via `scp` or manual copy
### Stages completed ### Stages completed
@ -36,7 +36,7 @@ against a TrueNAS SCALE 25.10 instance. Deployed on **maple.local** (10.0.0.138)
## File Map ## File Map
``` ```
truenas-burnin/ nas-burnin/
├── docker-compose.yml # two services: mock-truenas + app ├── docker-compose.yml # two services: mock-truenas + app
├── Dockerfile # app container ├── Dockerfile # app container
├── requirements.txt ├── requirements.txt
@ -222,18 +222,18 @@ All read from `.env` via `pydantic-settings`. See `.env.example` for full list.
### First deploy (already done) ### First deploy (already done)
```bash ```bash
# On maple.local # On maple.local
cd ~/docker/stacks/truenas-burnin cd ~/docker/stacks/nas-burnin
docker compose up -d --build docker compose up -d --build
``` ```
### Redeploy after code changes ### Redeploy after code changes
```bash ```bash
# Copy changed files from mac to maple.local first, e.g.: # Copy changed files from mac to maple.local first, e.g.:
scp -P 2225 -r app/ brandon@10.0.0.138:~/docker/stacks/truenas-burnin/ scp -P 2225 -r app/ brandon@10.0.0.138:~/docker/stacks/nas-burnin/
# Then on maple.local: # Then on maple.local:
ssh brandon@10.0.0.138 -p 2225 ssh brandon@10.0.0.138 -p 2225
cd ~/docker/stacks/truenas-burnin cd ~/docker/stacks/nas-burnin
docker compose up -d --build docker compose up -d --build
``` ```
@ -242,7 +242,7 @@ docker compose up -d --build
# On maple.local — stop containers first # On maple.local — stop containers first
docker compose stop app docker compose stop app
# Delete DB using alpine (container owns the file, sudo not available) # Delete DB using alpine (container owns the file, sudo not available)
docker run --rm -v ~/docker/stacks/truenas-burnin/data:/data alpine rm -f /data/app.db docker run --rm -v ~/docker/stacks/nas-burnin/data:/data alpine rm -f /data/app.db
docker compose start app docker compose start app
``` ```
@ -350,7 +350,7 @@ async def burnin_get(job_id: int, ...): ...
### `requirements.txt` is unpinned ### `requirements.txt` is unpinned
Every `docker compose up -d --build` pulls latest of fastapi, starlette, jinja2, asyncssh, etc. The Starlette 1.0 regression on 2026-04-27 is a direct consequence. **Either pin to known-good versions, or audit installed versions immediately after each rebuild** with: Every `docker compose up -d --build` pulls latest of fastapi, starlette, jinja2, asyncssh, etc. The Starlette 1.0 regression on 2026-04-27 is a direct consequence. **Either pin to known-good versions, or audit installed versions immediately after each rebuild** with:
```bash ```bash
docker exec truenas-burnin python3 -c "import fastapi, starlette, jinja2; print(fastapi.__version__, starlette.__version__, jinja2.__version__)" docker exec nas-burnin python3 -c "import fastapi, starlette, jinja2; print(fastapi.__version__, starlette.__version__, jinja2.__version__)"
``` ```
### Local source ↔ maple host can drift ### Local source ↔ maple host can drift
@ -358,8 +358,8 @@ The deploy convention is `scp -r app/` from mac to maple, but if you ever edit o
**Always `diff -u` before bulk scp:** **Always `diff -u` before bulk scp:**
```bash ```bash
ssh -p 2225 brandon@10.0.0.138 'cat ~/docker/stacks/truenas-burnin/app/routes.py' > /tmp/deployed_routes.py ssh -p 2225 brandon@10.0.0.138 'cat ~/docker/stacks/nas-burnin/app/routes.py' > /tmp/deployed_routes.py
diff -u /tmp/deployed_routes.py ~/Desktop/claudesandbox/truenas-burnin/app/routes.py diff -u /tmp/deployed_routes.py ~/Desktop/claudesandbox/nas-burnin/app/routes.py
``` ```
When sides have conflicting edits, prefer **patching the host file in place + rebuild** over a destructive scp. When sides have conflicting edits, prefer **patching the host file in place + rebuild** over a destructive scp.
@ -427,7 +427,7 @@ SMART attrs stored as JSON blob in `drives.smart_attrs`. Updated by `final_check
Settings page has a "Check for Updates" button that fetches: Settings page has a "Check for Updates" button that fetches:
``` ```
GET https://git.hellocomputer.xyz/api/v1/repos/brandon/truenas-burnin/releases/latest GET https://git.hellocomputer.xyz/api/v1/repos/brandon/nas-burnin/releases/latest
``` ```
Compares tag name against `settings.app_version`; shows "up to date" or "v{tag} available". Compares tag name against `settings.app_version`; shows "up to date" or "v{tag} available".

View file

@ -37,7 +37,7 @@ open http://localhost:8084 # or your host's IP
If you set `INITIAL_ADMIN_*` env vars *and* the users table is empty, that If you set `INITIAL_ADMIN_*` env vars *and* the users table is empty, that
account is created on startup automatically. After that the env vars are account is created on startup automatically. After that the env vars are
ignored — change passwords from the UI ("Change password" header link) or ignored — change passwords from the UI ("Change password" header link) or
the CLI (`docker exec -it truenas-burnin python -m app.auth_cli reset the CLI (`docker exec -it nas-burnin python -m app.auth_cli reset
<username>`). <username>`).
--- ---
@ -172,17 +172,17 @@ Configure SMTP in Settings → Email. Includes a "Test SMTP" button.
### Logs ### Logs
```bash ```bash
docker logs -f truenas-burnin docker logs -f nas-burnin
# JSON-structured. Filter with jq: # JSON-structured. Filter with jq:
docker logs truenas-burnin 2>&1 | jq -rR 'fromjson? | "\(.ts) \(.level) \(.msg)"' docker logs nas-burnin 2>&1 | jq -rR 'fromjson? | "\(.ts) \(.level) \(.msg)"'
``` ```
### User management ### User management
```bash ```bash
docker exec -it truenas-burnin python -m app.auth_cli list docker exec -it nas-burnin python -m app.auth_cli list
docker exec -it truenas-burnin python -m app.auth_cli add <username> docker exec -it nas-burnin python -m app.auth_cli add <username>
docker exec -it truenas-burnin python -m app.auth_cli reset <username> docker exec -it nas-burnin python -m app.auth_cli reset <username>
``` ```
Passwords are read from a TTY prompt; never accept them on the command Passwords are read from a TTY prompt; never accept them on the command

View file

@ -251,8 +251,8 @@ The API makes this app a strong candidate for MCP server integration, allowing a
Docker Compose. Minimum viable setup: Docker Compose. Minimum viable setup:
```bash ```bash
git clone https://github.com/yourusername/truenas-burnin git clone https://github.com/yourusername/nas-burnin
cd truenas-burnin cd nas-burnin
cp .env.example .env cp .env.example .env
# Edit .env for system-level settings (TrueNAS URL, poll interval, etc.) # Edit .env for system-level settings (TrueNAS URL, poll interval, etc.)
docker compose up -d docker compose up -d

View file

@ -1,9 +1,9 @@
"""Password reset / user management CLI. """Password reset / user management CLI.
Run inside the container: Run inside the container:
docker exec -it truenas-burnin python -m app.auth_cli reset <username> docker exec -it nas-burnin python -m app.auth_cli reset <username>
docker exec -it truenas-burnin python -m app.auth_cli list docker exec -it nas-burnin python -m app.auth_cli list
docker exec -it truenas-burnin python -m app.auth_cli add <username> docker exec -it nas-burnin python -m app.auth_cli add <username>
Reads the password from a TTY prompt never accept it on the command Reads the password from a TTY prompt never accept it on the command
line so it doesn't leak into shell history. line so it doesn't leak into shell history.

View file

@ -83,7 +83,7 @@ class Settings(BaseSettings):
ssh_key: str = "" # PEM private key content (paste full key including headers) ssh_key: str = "" # PEM private key content (paste full key including headers)
# Application version — used by the /api/v1/updates/check endpoint # Application version — used by the /api/v1/updates/check endpoint
app_version: str = "1.0.0-40" app_version: str = "1.0.0-41"
# ---- Authentication (1.0.0-22) ---- # ---- Authentication (1.0.0-22) ----
# session_secret: HMAC key for signing session cookies. Empty = generate # session_secret: HMAC key for signing session cookies. Empty = generate

View file

@ -112,7 +112,7 @@ async def check_updates():
try: try:
async with httpx.AsyncClient(timeout=8.0) as client: async with httpx.AsyncClient(timeout=8.0) as client:
r = await client.get( r = await client.get(
"https://git.hellocomputer.xyz/api/v1/repos/brandon/truenas-burnin/releases/latest", "https://git.hellocomputer.xyz/api/v1/repos/brandon/nas-burnin/releases/latest",
headers={"Accept": "application/json"}, headers={"Accept": "application/json"},
) )
if r.status_code == 200: if r.status_code == 200:

View file

@ -59,7 +59,7 @@
<div class="login-footer"> <div class="login-footer">
Authentication is local to this dashboard. Forgot your password? Authentication is local to this dashboard. Forgot your password?
Reset it via the container DB:<br> Reset it via the container DB:<br>
<code class="login-code">docker exec truenas-burnin python -m app.auth_cli reset &lt;user&gt;</code> <code class="login-code">docker exec nas-burnin python -m app.auth_cli reset &lt;user&gt;</code>
</div> </div>
</main> </main>

View file

@ -343,7 +343,7 @@
<div id="restart-banner" style="display:none;margin-top:12px;padding:12px 16px;background:rgba(255,170,0,0.12);border:1px solid var(--yellow);border-radius:8px;color:var(--text-strong)"> <div id="restart-banner" style="display:none;margin-top:12px;padding:12px 16px;background:rgba(255,170,0,0.12);border:1px solid var(--yellow);border-radius:8px;color:var(--text-strong)">
<strong>&#9888; Container restart required</strong> — system settings are saved but won't take effect until you restart the app container: <strong>&#9888; Container restart required</strong> — system settings are saved but won't take effect until you restart the app container:
<pre style="margin:8px 0 0;padding:8px 10px;background:var(--bg-card);border-radius:5px;font-size:12px;color:var(--text-strong);user-select:all">docker compose restart app</pre> <pre style="margin:8px 0 0;padding:8px 10px;background:var(--bg-card);border-radius:5px;font-size:12px;color:var(--text-strong);user-select:all">docker compose restart app</pre>
<span style="font-size:11px;color:var(--text-muted)">Run this on <strong>maple.local</strong> from <code>~/docker/stacks/truenas-burnin/</code></span> <span style="font-size:11px;color:var(--text-muted)">Run this on <strong>maple.local</strong> from <code>~/docker/stacks/nas-burnin/</code></span>
</div> </div>
</form> </form>

View file

@ -11,7 +11,7 @@ services:
app: app:
build: . build: .
container_name: truenas-burnin container_name: nas-burnin
ports: ports:
- "8084:8084" - "8084:8084"
env_file: .env env_file: .env

View file

@ -14,7 +14,7 @@
set -euo pipefail set -euo pipefail
REMOTE_HOST="${REMOTE_HOST:-maple}" REMOTE_HOST="${REMOTE_HOST:-maple}"
CONTAINER="${CONTAINER:-truenas-burnin}" CONTAINER="${CONTAINER:-nas-burnin}"
REMOTE_TMP="/tmp/tnb-tests-$$.tgz" REMOTE_TMP="/tmp/tnb-tests-$$.tgz"
CONTAINER_TMP="/tmp/tnb-tests.tgz" CONTAINER_TMP="/tmp/tnb-tests.tgz"
PATTERN="${1:-}" PATTERN="${1:-}"

View file

@ -1,5 +1,5 @@
[Unit] [Unit]
Description=Security scan of truenas-burnin (pip-audit + bandit + gitleaks) Description=Security scan of nas-burnin (pip-audit + bandit + gitleaks)
After=network-online.target docker.service After=network-online.target docker.service
Wants=network-online.target Wants=network-online.target
@ -7,7 +7,7 @@ Wants=network-online.target
Type=oneshot Type=oneshot
# Wire SECURITY_SCAN_WEBHOOK here if you want findings POSTed somewhere. # Wire SECURITY_SCAN_WEBHOOK here if you want findings POSTed somewhere.
# Environment=SECURITY_SCAN_WEBHOOK=https://chat.example/hooks/abc # Environment=SECURITY_SCAN_WEBHOOK=https://chat.example/hooks/abc
ExecStart=%h/docker/stacks/truenas-burnin/scripts/security-scan.sh ExecStart=%h/docker/stacks/nas-burnin/scripts/security-scan.sh
# Tools cache + container pulls — give them headroom. # Tools cache + container pulls — give them headroom.
TimeoutStartSec=600 TimeoutStartSec=600
StandardOutput=journal StandardOutput=journal

View file

@ -1,5 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Daily security scan of the deployed truenas-burnin source on maple. # Daily security scan of the deployed nas-burnin source on maple.
# Mirrors the .forgejo/workflows/security-scan.yml CI pipeline so a finding # Mirrors the .forgejo/workflows/security-scan.yml CI pipeline so a finding
# the runner-less forge would have flagged still surfaces here. # the runner-less forge would have flagged still surfaces here.
# #
@ -18,8 +18,8 @@
set -uo pipefail set -uo pipefail
REPO_URL="${REPO_URL:-https://git.hellocomputer.xyz/brandon/truenas-burnin.git}" REPO_URL="${REPO_URL:-https://git.hellocomputer.xyz/brandon/nas-burnin.git}"
REPO="${REPO:-$HOME/scan-checkouts/truenas-burnin}" REPO="${REPO:-$HOME/scan-checkouts/nas-burnin}"
OUT_BASE="${OUT_BASE:-$HOME/security-scans}" OUT_BASE="${OUT_BASE:-$HOME/security-scans}"
DATE="$(date +%Y-%m-%d)" DATE="$(date +%Y-%m-%d)"
OUT_DIR="$OUT_BASE/scan-$DATE" OUT_DIR="$OUT_BASE/scan-$DATE"
@ -29,7 +29,7 @@ GITLEAKS_VERSION="${GITLEAKS_VERSION:-8.21.2}"
mkdir -p "$OUT_DIR" "$(dirname "$REPO")" mkdir -p "$OUT_DIR" "$(dirname "$REPO")"
# Maintain a dedicated checkout for scanning. The deploy at # Maintain a dedicated checkout for scanning. The deploy at
# ~/docker/stacks/truenas-burnin/ is just the bind-mounted source — no # ~/docker/stacks/nas-burnin/ is just the bind-mounted source — no
# .git, no history — so gitleaks can't scan there. We keep a separate # .git, no history — so gitleaks can't scan there. We keep a separate
# clone, fast-forward it to origin/main each run. # clone, fast-forward it to origin/main each run.
if [ ! -d "$REPO/.git" ]; then if [ ! -d "$REPO/.git" ]; then
@ -58,7 +58,7 @@ date -Iseconds >> "$OUT_DIR/summary.txt"
echo >> "$OUT_DIR/summary.txt" echo >> "$OUT_DIR/summary.txt"
# --- pip-audit against the lockfile in a throwaway container ------------ # --- pip-audit against the lockfile in a throwaway container ------------
# Previously we did `docker exec truenas-burnin pip install pip-audit` # Previously we did `docker exec nas-burnin pip install pip-audit`
# which mutated the live production container with a transient package. # which mutated the live production container with a transient package.
# Now scan the lockfile in an ephemeral container — same coverage of # Now scan the lockfile in an ephemeral container — same coverage of
# pinned versions + their transitives, no side effects on prod. # pinned versions + their transitives, no side effects on prod.
@ -77,7 +77,7 @@ echo " exit=$PIPS ($OUT_DIR/pip-audit.txt)" | tee -a "$OUT_DIR/summary.txt"
# forge HEAD and maple. B608 (SQL injection via dynamic strings) is # forge HEAD and maple. B608 (SQL injection via dynamic strings) is
# skipped globally: every dynamic SQL build in this codebase uses # skipped globally: every dynamic SQL build in this codebase uses
# bound parameters for data and structural placeholders only. # bound parameters for data and structural placeholders only.
DEPLOY_DIR="${DEPLOY_DIR:-$HOME/docker/stacks/truenas-burnin}" DEPLOY_DIR="${DEPLOY_DIR:-$HOME/docker/stacks/nas-burnin}"
echo "--- bandit (deploy: $DEPLOY_DIR) ---" | tee -a "$OUT_DIR/summary.txt" echo "--- bandit (deploy: $DEPLOY_DIR) ---" | tee -a "$OUT_DIR/summary.txt"
docker run --rm \ docker run --rm \
-v "$DEPLOY_DIR/app:/src:ro" \ -v "$DEPLOY_DIR/app:/src:ro" \

View file

@ -1,5 +1,5 @@
[Unit] [Unit]
Description=Daily security scan of truenas-burnin Description=Daily security scan of nas-burnin
Requires=security-scan.service Requires=security-scan.service
[Timer] [Timer]