The service at https://lessons.pleion.dev/ serves a public page and an
embeddable iframe for an edu-00 lesson document, played back by
apps/flutter3d_lesson_viewer.
It has its own subdomain and its own service on bob, separate from
models.pleion.dev (cloud/server). The audience is different: a lesson
embedded in someone else's course page, where cloud/server hosts a person's
own model cabinet. So is the security posture. /e/<slug> exists so that a
third-party page can frame it, and cloud/server forbids exactly that for
its own pages.
server/ |
One Dart process: shelf routes and plain HTML strings. No jaspr, no database, no accounts |
server/lib/src/lessons_registry.dart |
The lessons this service knows about, as Dart data. It is not a file on disk or a table, because nobody uploads a lesson here (yet; see "What is not here yet") |
tool/ |
Building the executable, building the viewer, deploying both |
deploy/ |
The systemd units, the nginx vhost, the tunnel config and an example environment |
cd cloud/lessons/server
dart pub get
LESSONS_BASE_URL=http://localhost:8796 \
dart run lib/main.server.dartOpen http://localhost:8796/l/engine-tour. Without LESSONS_VIEWER_DIR set,
/app/ answers 404. To see the lesson itself run, and not just the page
around it, build the viewer first (cloud/lessons/tool/build_viewer.sh, which
needs Flutter) and set LESSONS_VIEWER_DIR=build/app.
| Variable | |
|---|---|
LESSONS_BASE_URL |
What the embed snippet on /l/<slug> tells people to point their <iframe> at |
LESSONS_PORT |
Default 8796 |
LESSONS_VIEWER_DIR |
Optional; serves the lesson viewer at /app/ when set |
Every setting is read once at start. If a required one is missing, the start stops and names everything that is missing.
dart testThe tests cover routes only. There is no database, so there is no
-t db/-x db split of the kind cloud/server needs.
Add a Lesson to lessons in lib/src/lessons_registry.dart, and bundle its
level JSON as an asset of apps/flutter3d_lesson_viewer (see that app's own
assets/levels/). There is no upload path and no database migration. This is
as far as the "public page for one lesson" acceptance goes today.
cloud/lessons/tool/deploy.shThe script builds the executable in a linux/amd64 Dart container, builds the
viewer, rsyncs both to bob:/opt/flutter3d-lessons and restarts the unit.
Nothing else changes on a redeploy.
These steps are done once, by hand, for the same reason as the "first time"
section of cloud/README.md: each one creates something outside this
repository, and a script that repeats them is a script somebody runs twice by
accident.
-
Environment: copy
deploy/flutter3d-lessons.env.exampleto/etc/flutter3d-lessons.env, mode 600, with the real base URL. -
Service: copy
deploy/flutter3d-lessons.serviceto/etc/systemd/system/, runsystemctl enable flutter3d-lessons, then runtool/deploy.sh. -
nginx: copy
deploy/nginx-lessons.pleion.dev.conftosites-available, link it intosites-enabled, thennginx -t && systemctl reload nginx. -
Tunnel: create a separate tunnel for this service instead of adding an ingress rule to the models tunnel:
cloudflared tunnel create flutter3d-lessons
Put the printed ID into
deploy/cloudflared-lessons.yml, copy that to/etc/cloudflared/lessons.yml, then add the DNS route with the config and the tunnel ID named explicitly.cloud/README.mdalready documents the mistake this flag avoids: a baretunnel route dnspicks up bob's default config and points the record at the wrong tunnel.cloudflared --config /etc/cloudflared/lessons.yml \ tunnel route dns --overwrite-dns <TUNNEL_ID> lessons.pleion.dev
Then enable
deploy/cloudflared-lessons.service.
- Executable:
bob:/opt/flutter3d-lessons/lessons, run byflutter3d-lessons.serviceas a dynamic user - nginx:
127.0.0.1:8795, serving/app/from disk and proxying everything else to the service on127.0.0.1:8796 - Tunnel:
cloudflared-lessons.service
The ports are the next free pair after the 8793/8794 used by
models.pleion.dev. Before the first deploy, confirm nothing else on bob
already holds 8795/8796; this repository only tracks what it put there
itself.
Why /e/ needs its own nginx location, not just its own route in the
Dart service. dart:io's HttpServer adds X-Frame-Options: SAMEORIGIN
to every response by default, at the transport layer. shelf (and this
service's own _securityHeaders) cannot remove a header that dart:io
pre-populated outside shelf's own header map, so leaving it out of the
Response this service builds does not mean it is absent on the wire. This
was found by curling the deployed service, not by reading the code. The fix
is in the location /e/ block of deploy/nginx-lessons.pleion.dev.conf
(proxy_hide_header X-Frame-Options). Removing that block as redundant,
on the grounds that the Dart route already omits the header, would silently
break every embed.
prep-00/prep-01/prep-02(doc/lesson-scenarios-plan.md): a real object to tear down, real lesson text, real questions. The one lesson this service ships today is the camera tour already proved bytpl-04'sviewer.json, plus one placeholdercheckquestion added to prove that mechanic against real content. It is not a real course.- Everything
apps/flutter3d_lesson_vieweritself does not play back:offsets(layered teardown),edu_clip_planerendering,bindings/edu_data_source(live data). See that app's ownlib/main.dartdoc comment andapps/flutter3d_lesson_viewer/lib/src/lesson_player.dart. check(the quiz question):check_prompt.dartimplements and unit-tests it, and the shippedtour.jsonstill carries aquiz-stepsentity with a real question, but the step is deliberately left OUT ofedu_sequence.steps. A real-browser run (notflutter test) hit Flutter's default red error screen on that step, and the root cause is not yet found; see theedu-02entry indoc/tooling-plan.md. Re-add it tostepsonly after someone with a working browser reproduces and fixes the crash.edu-03(LTI/xAPI): this service has no notion of a student identity or a grade to report.edu-03depends on this service but has not started.- Uploading a lesson. The registry is Dart code because nobody has asked
to publish their own lesson here yet.
cloud/server's own README names the same gap for its "public catalogue".