Files
truenas-stacks/home/doris-schoolhouse/README.md
2026-05-16 20:33:31 +00:00

9.2 KiB

Doris Schoolhouse

Desktop-first school operations app for John.

What it is

Doris Schoolhouse is the front door for John's college workflow. It pulls scattered school chores into one place so Doris can handle intake, metadata, archiving, and later grade follow-up without bouncing between D2L, file uploads, recordings, and Paperless by hand.

In plain English, this stack exists to answer:

  • what classwork is due
  • where a submission or recording should be filed
  • how that item should be archived and tagged
  • whether grading/comments later made it back onto the archived record

What it does

Schoolhouse gives John a local UI to:

  • upload assignment turn-ins, with programming-class source files bundled into one archived submission artifact
  • upload class recordings
  • confirm suggested metadata
  • track assignment status through grading
  • sync classes/assignments/grades/comments from D2L
  • archive submissions/transcripts/summaries in Paperless

Why it exists

The point is not just "another web app." The point is to make school intake boring and repeatable.

  • D2L is the source for classes, assignments, grades, and comments.
  • Schoolhouse is the operational layer where uploads, recordings, and metadata are managed.
  • Paperless is the long-term archive for receipts, transcripts, summaries, and school documents.
  • n8n handles the workflow glue between intake events and downstream processing.

That split keeps John from doing the same clerical nonsense over and over, and gives Doris one consistent place to reason about schoolwork.

Runtime model

  • Repo source: home/doris-schoolhouse
  • Live NOMAD runtime: /opt/doris-schoolhouse
  • Published app port: 8091
  • Public path: schoolhouse.paccoco.com behind Pangolin OAuth
  • Storage: shared Postgres in production, JSON fallback for local iteration
  • Archive path: Paperless + n8n school workflows

Do not run production from an agent workspace. Repo source is for editing; /opt/doris-schoolhouse is the live runtime.

How it works

  • FastAPI
  • Jinja2 templates
  • shared Postgres
  • Pangolin-authenticated public route
  • existing Paperless and n8n school workflows
  • bundled ffmpeg for recording conversion inside the container

Typical flow:

  1. D2L data is synced into Schoolhouse.
  2. John uploads an assignment or recording through Schoolhouse.
  3. Schoolhouse normalizes the intake payload and keeps local/runtime state.
  4. n8n receives the handoff and pushes the right artifacts into Paperless.
  5. Paperless becomes the durable archive.
  6. Later grade/comment updates can be reflected back onto the archived record.

Programming-class uploads get special handling: multiple source files are bundled into one canonical source archive, with manifest and receipt/listing artifacts so the repo of what was turned in stays coherent.

Current state

Schoolhouse is live on NOMAD and already covers the real workflow, not just a mockup.

For local iteration, the source tree still supports a JSON-backed mode:

  • default state dir: /data/state
  • default upload temp dir: /data/uploads

For local host-side testing, override with env vars such as:

export SCHOOLHOUSE_STATE_DIR=/tmp/doris-schoolhouse-state
export UPLOAD_TMP_DIR=/tmp/doris-schoolhouse-uploads

Production runtime uses shared Postgres plus Paperless/n8n integration.

SCHOOLHOUSE_STORAGE_BACKEND=auto prefers Postgres when psycopg and a PostgreSQL DSN are available, and falls back to JSON state for local iteration.

This repo directory contains:

  • the FastAPI app and templates
  • schema/bootstrap helpers
  • compose/env examples
  • D2L, Paperless, and n8n integration code

Layout

home/doris-schoolhouse/
├── .env.example
├── docker-compose.yaml
├── requirements.txt
└── app/
    ├── main.py
    ├── config.py
    ├── models.py
    ├── db/schema.sql
    ├── routes/
    ├── services/
    ├── static/
    └── templates/

Environment and integration helpers

Useful helpers now included:

bin/check-env.sh
bin/apply-postgres-schema.sh
bin/test-assignment-handoff.sh <file> [more_files ...] <class_name> [assignment_name]
bin/test-recording-handoff.sh <file> <class_name>

These are safe prep helpers. The handoff scripts are dry-run previews; they print the exact curl commands rather than firing them automatically. Assignment intake accepts either a legacy single file field or the newer multi-file files field. Programming-class uploads are normalized into one source-bundle zip per submission version, with a manifest and source-listing sidecar kept alongside the bundle locally. For Paperless archival, Schoolhouse uploads a generated PDF submission receipt/listing while keeping the real source bundle zip as the local canonical artifact.

Browser submits now auto-confirm into Paperless before redirecting back to /assignments/upload. The success notice reports the resulting Paperless queue state, so a normal web upload should no longer stop at uploaded-local unless archival actually failed.

Serenity backup

On NOMAD, the canonical local Schoolhouse artifacts live under /opt/doris-schoolhouse/data. The helper bin/backup-to-serenity.sh mounts Serenity's NFS export at /mnt/serenity-data if needed and mirrors that data tree into:

/mnt/serenity-data/backups/nomad/doris-schoolhouse/data

Recommended daily cron on NOMAD:

40 2 * * * /opt/doris-schoolhouse/bin/backup-to-serenity.sh >> /opt/doris-schoolhouse/logs/backup-to-serenity.log 2>&1

This keeps the real source bundles, receipt PDFs, manifests, and other Schoolhouse runtime data copied onto Serenity without depending on broken SSH trust from NOMAD.

Helper scripts

Local iteration helpers:

bin/bootstrap-local.sh
bin/run-local.sh
bin/process-recording.sh <recording_id>

These default to the JSON backend and /tmp paths unless you override the env vars.

Bootstrap local state quickly

For local iteration without live Postgres:

cd /home/fizzlepoof/repos/truenas-stacks/home/doris-schoolhouse
export SCHOOLHOUSE_STORAGE_BACKEND=json
export SCHOOLHOUSE_STATE_DIR=/tmp/doris-schoolhouse-state
export UPLOAD_TMP_DIR=/tmp/doris-schoolhouse-uploads
PYTHONPATH=. python3 app/scripts/bootstrap_state.py

That seeds local state from the latest D2L snapshot.

Recording worker helper

There is now a local worker entrypoint for WAV → MP3 processing:

cd /home/fizzlepoof/repos/truenas-stacks/home/doris-schoolhouse
export SCHOOLHOUSE_STORAGE_BACKEND=json
export SCHOOLHOUSE_STATE_DIR=/tmp/doris-schoolhouse-state
export UPLOAD_TMP_DIR=/tmp/doris-schoolhouse-uploads
PYTHONPATH=. python3 app/scripts/process_recording.py <recording_id>

If ffmpeg is not installed, the worker records a clean blocked result with the exact conversion command it wanted to run.

Local validation

cd /home/fizzlepoof/repos/truenas-stacks/home/doris-schoolhouse
python3 -m py_compile app/main.py app/config.py app/models.py app/routes/*.py app/services/*.py
python3 - <<'PY'
import yaml
from pathlib import Path
print(yaml.safe_load(Path('docker-compose.yaml').read_text())['services'].keys())
PY

Container build

The source now includes a Dockerfile so the app can run with its own dependencies instead of relying on the host.

That image includes:

  • Python dependencies from requirements.txt
  • ffmpeg
  • curl for healthchecks

So even if the host is missing ffmpeg, the containerized runtime can still do WAV → MP3 conversion.

Deploy shape

Expected live deploy flow on NOMAD:

sudo mkdir -p /opt/doris-schoolhouse/data
sudo rsync -a --delete --exclude '.env' --exclude 'data/' /home/fizzlepoof/repos/truenas-stacks/home/doris-schoolhouse/ /opt/doris-schoolhouse/
cd /opt/doris-schoolhouse
sudo cp .env.nomad.example .env
docker compose --env-file .env config
docker compose --env-file .env up -d --build

That flow is the reference for refreshing the live runtime from repo source.

Live integration notes

Current verified live behavior:

  • https://n8n.paccoco.com/webhook/school/intake/upload accepts Doris Schoolhouse multipart assignment uploads and returned a successful live smoke-test response on 2026-05-15.
  • https://n8n.paccoco.com/webhook/class/upload now succeeds in live smoke testing after upstream workflow and LiteLLM fixes on 2026-05-15.
  • Direct Whisper testing against http://10.5.1.16:8786/v1/audio/transcriptions succeeded with a tiny MP3.
  • Direct LiteLLM testing against http://10.5.1.6:4000/v1/chat/completions now succeeds for model medium after the PD route fix.

The app now captures webhook HTTP failures as structured responses instead of crashing blindly.

NOMAD-specific deployment notes

On NOMAD, Schoolhouse should run as a standalone service from /opt/doris-schoolhouse.

Unlike PD stacks, this host does not currently use the PD Docker external networks for Pangolin or shared databases. The practical deploy model here is:

  • publish host port 8091
  • point Pangolin/Newt at that host port
  • reach shared Postgres on PD over 10.5.1.6:5432

Use .env.nomad.example as the starting point for the live runtime.

Deployment references

  • DEPLOYMENT.md — clean NOMAD rollout notes
  • .env.nomad.example — compose-oriented runtime example for NOMAD