The website for the Aether programming language, and its interactive REPL,
served by Aether's own std.http server.
serve.ae serves the static site and the REPL API. It replaces the original
Go/Echo backend entirely.
- Static pages and assets from
static/. GET /api/lesson/:nbrreturns a lesson{ title, theory, starter_code }fromresources/lessons/.PUT /api/execcompiles and runs the submitted source withae runand returns{ stdout, stderr }. This is the live playground backend.GET /healthzliveness.
It uses std.http (server, routing, zero-copy static files), std.io
(lesson files off disk), and std.json (escaped responses).
ae run serve.ae # http://localhost:1323/
or build a binary and run it from the repository root, so static/ and
resources/ resolve:
ae build serve.ae -o aesite-serve
./aesite-serve
./test.sh
Builds serve.ae, starts it, exercises every route, and exits non-zero on
any failure. CI runs exactly this.
Pushes to main run .github/workflows/deploy.yml:
- build
serve.aeand run./test.sh, - publish
static/to GitHub Pages at aether-lang.dev.
The custom domain is set by static/CNAME. On the registrar, point the apex
at GitHub Pages with four A records: 185.199.108.153, 185.199.109.153,
185.199.110.153, 185.199.111.153.
The live REPL backend (/api/exec running real Aether) is the next step. It
deploys as the serve.ae server on a host of its own, since Pages serves
static content only.
static/repl.html is a self-contained playground. Lessons load from
static/lessons.json (baked by tools/genlessons.ae, which also stores each
lesson's real output), so lessons, theory, and a sample run work on a static
host such as GitHub Pages with no server.
On load it probes /healthz. If a live serve.ae backend answers, Run
executes whatever is in the editor against it; otherwise Run shows the
precomputed output for the unedited lesson and points you at ae run for
local execution. To target a backend on another origin, set its URL in the
<meta name="aesite-exec"> tag and allow CORS there.
Regenerate lessons after editing resources/lessons/:
ae run tools/genlessons.ae
The static site works on its own. To make the playground run edited code, deploy
serve.ae as a backend and point the site at it.
Deploy with the included Dockerfile (the image keeps the Aether toolchain so
/api/exec can compile and run code). On Fly.io:
fly launch --no-deploy # once, creates the app from fly.toml
fly deploy
Then set the backend origin in static/repl.html:
<meta name="aesite-exec" content="https://your-app.fly.dev">
The playground probes /healthz and, when it answers, runs the editor's code there.
/api/exec runs ARBITRARY submitted code, i.e. arbitrary native code on the
host. It is hardened, not sandboxed: each request compiles in a fresh temp dir,
then the binary runs as a non-root user in a private network namespace
(unshare, no outbound access) under ulimits (CPU, file size) and a 10s
timeout, with output capped. Aether's containment model is language-level and
does not constrain untrusted external programs, so that is deliberately not
relied on here. A public deployment MUST add real OS isolation, e.g.
docker run --rm --read-only --tmpfs /tmp --pids-limit=256 \
--memory=512m --cpus=1 -p 8080:8080 aesite
plus rate limiting, and ideally a per-request microVM (Firecracker/gVisor). Run it somewhere disposable, never on a host with secrets or writable mounts.
static/Docs/ is generated from the language's markdown docs. Regenerate after
they change:
python3 tools/gendocs.py ~/Documents/git/aether/docs static/Docs
It converts every top-level *.md to a styled page (grouped sidebar, syntax
highlighting, cross-links rewritten to /Docs/*.html) and refreshes
static/docs.html. Output is committed and served as-is by Pages.
static/ae.svg is the ae ligature, the org's mark, traced from the avatar
and trimmed to the glyph so it carries no background of its own. The .ae rule
in static/aether.css masks it and paints it --red; font-size stays the
sizing knob. It needs about 30px to read: below that the hairlines close up.
The stylesheet is inlined into every page, so after editing aether.css:
python3 tools/inline_css.py
The card and the tab icon are pages too (tools/og.html, tools/favicon.html)
that link aether.css, so they cannot drift from the site's own mark and
palette. After editing either:
python3 tools/render_assets.py
serve.ae # the Aether server
test.sh # integration smoke test
static/ # HTML, CSS, ae.svg, Docs/, CNAME
resources/lessons/<n>/ # content.html, code.ae, title.txt
tools/gendocs.py # markdown -> static/Docs/ generator
tools/genlessons.ae # resources/lessons -> static/lessons.json
tools/inline_css.py # aether.css -> every page's <head>
tools/og.html, favicon.html # templates for og.png and favicon.png
tools/render_assets.py # renders both to static/
Dockerfile, fly.toml # optional live exec backend
.github/workflows/ # CI + Pages deploy
When aether publishes a release, its pipeline dispatches aether-release to
this repo. The sync aether release workflow then checks out that tag,
regenerates static/Docs from its docs/, stamps the version from its
VERSION file, moves the toolchain pin in deploy.yml to the new tag,
commits, and dispatches deploy.yml to publish.
Doing this by hand meant doing it late: the site sat on v0.562 while aether shipped 0.580.
Three things worth knowing:
- The daily schedule is the backstop. The cross-repo dispatch needs a PAT
on the aether side (
AESITE_SYNC_TOKEN,contents: writehere). Without it the release still succeeds and says so, and this repo's 06:15 UTC check picks the release up within a day. A missing token delays the site; it does not stall it. static/Docsis rebuilt from empty each time, so a doc deleted upstream stops being served. Everything in that directory is generated — do not put anything hand-written there.- The deploy is still gated.
deploy.ymlbuildsserve.aewith the new toolchain and runs the integration test before publishing. If a release breaks the site, nothing is deployed and the previous version stays up.
To publish a release by hand, run the sync aether release workflow and give
it a tag, or leave the tag empty to take the latest release.