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:
parent
d38807f957
commit
944e4c1541
13 changed files with 37 additions and 37 deletions
22
CLAUDE.md
22
CLAUDE.md
|
|
@ -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".
|
||||||
|
|
||||||
|
|
|
||||||
12
README.md
12
README.md
|
|
@ -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
|
||||||
|
|
|
||||||
4
SPEC.md
4
SPEC.md
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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 <user></code>
|
<code class="login-code">docker exec nas-burnin python -m app.auth_cli reset <user></code>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>⚠ Container restart required</strong> — system settings are saved but won't take effect until you restart the app container:
|
<strong>⚠ 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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:-}"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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" \
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue