254 lines
9.2 KiB
Markdown
254 lines
9.2 KiB
Markdown
# 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:
|
|
|
|
```bash
|
|
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
|
|
|
|
```text
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```cron
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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.30.7:8786/v1/audio/transcriptions` succeeded with a tiny MP3.
|
|
- Direct LiteLLM testing against `http://10.5.30.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.30.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
|