Notebooks from the marimo server can now appear in the garden. A notebook
opts in with an HTML comment in one of its markdown cells — invisible when
rendered, greppable in the .py source:
<!-- garden:publish
title: Dream of Spotification
order: 30
-->
Default-deny on purpose: garden.c0smere.net is public and the export bakes
each notebook's executed output into the page, not just its code.
export-notebooks.py also carries a NEVER_PUBLISH list (genome_*, coursework)
so a marker pasted into one of those refuses loudly instead of publishing.
Pipeline: export-notebooks.py runs each marked notebook in a one-shot
marimo container (same image/env/GPU as the live server, so it hits the real
databases) into notebooks-export/ + index.json; copy-notebooks.mjs stages
those to public/nb/; Astro reads index.json to build the pages and the
sidebar section.
Isolation choices worth keeping:
- Own timer and own lock, separate from the 5-min garden build — executing a
notebook takes minutes and must never hold up a build tick.
- Cached on notebook content; both index.json and .cache.json go through
write_if_changed, since auto-build.sh hashes mtimes under notebooks-export/
and an unconditional rewrite would force a full rebuild every 30 minutes.
- Per-notebook timeout; a failure keeps the previous export and continues.
- Runs against a throwaway copy of the notebook dir, so notebooks that write
scratch files don't dirty the notebooks repo.
- Notebooks are NOT injected into the garden collection — they aren't vault
notes, and doing so would move noteCount and the sitemap.
build.format:'file' makes the listing a file (notebooks.html) beside a
directory of detail pages. Verified against the running nginx: /notebooks and
/notebooks/<slug> resolve through the generic try_files but /notebooks/ does
not, so nginx.conf gets an explicit location for the trailing-slash form.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
110 lines
5.0 KiB
Markdown
110 lines
5.0 KiB
Markdown
# garden-astro
|
|
|
|
Astro rebuild of the digital garden at `garden.c0smere.net`, replacing
|
|
Quartz v4. Content stays in the Obsidian vault (`Digital_Garden/` folder);
|
|
this repo is only the rendering pipeline.
|
|
|
|
## URL compatibility
|
|
|
|
Built to be a drop-in replacement — every URL on the live Quartz site
|
|
resolves identically here (verified against the live sitemap, 102/102):
|
|
|
|
- case preserved, spaces become `-`, hyphens/em-dashes kept
|
|
- leaf pages: `/Folder/Note-Name` (`build.format: 'file'` + nginx
|
|
`try_files $uri $uri.html`)
|
|
- folder indexes: `/Folder/` (from `Folder/index.md`, or a synthetic
|
|
listing page when no index exists)
|
|
|
|
## Obsidian compatibility (`src/lib/remark-obsidian.mjs`)
|
|
|
|
- `[[wikilinks]]` with aliases/anchors, resolved vault-path-first then by
|
|
basename; unresolved links degrade to plain text
|
|
- `![[image embeds]]` and relative `` both rewrite to flat
|
|
`/assets/<name>` URLs; `scripts/copy-assets.mjs` resolves the files
|
|
(Digital_Garden first, then vault-wide) and copies them in at build time
|
|
- `> [!type]` callouts, KaTeX math, mermaid (lazy-loaded client-side only
|
|
on pages that use it), shiki dual light/dark themes
|
|
- `draft:` frontmatter excluded, including Quartz-style quoted `"true"`
|
|
|
|
## Build
|
|
|
|
```sh
|
|
GARDEN_CONTENT=/path/to/vault/Digital_Garden npm run build
|
|
```
|
|
|
|
Defaults to the kotov vault copy. On cyrion use `deploy/build.sh`
|
|
(containerized node:24, vault mounted read-only), then
|
|
`docker compose -f deploy/docker-compose.yml up -d` serves `dist/` on
|
|
port 18100, proxied as `garden.c0smere.net` (NPM public + Caddy internal).
|
|
|
|
## marimo notebooks (`/notebooks`)
|
|
|
|
Notebooks from the marimo server (`/home/nox/docker/marimo/notebooks/`) can
|
|
be published into the garden. **A notebook publishes only if it opts in**, by
|
|
carrying this HTML comment in one of its markdown cells — invisible in the
|
|
rendered page, greppable in the `.py` source:
|
|
|
|
```
|
|
<!-- garden:publish
|
|
title: Dream of Spotification
|
|
description: A decade of Spotify history, worked two ways.
|
|
order: 30
|
|
-->
|
|
```
|
|
|
|
Only the `garden:publish` line is required. Optional keys: `title`,
|
|
`description`, `order` (sort weight, default 100), `slug` (URL override,
|
|
default derived from the filename), `timeout` (export seconds, default 600).
|
|
Delete the marker to unpublish — the next export run removes the page.
|
|
|
|
This is deliberately default-deny: garden.c0smere.net is public and the
|
|
export bakes each notebook's **executed output** into the page, not just its
|
|
code. `deploy/export-notebooks.py` also carries a `NEVER_PUBLISH` list
|
|
(`genome_*`, coursework) as a second latch, so a marker pasted into one of
|
|
those refuses loudly instead of publishing.
|
|
|
|
Pipeline:
|
|
|
|
1. `deploy/export-notebooks.py` (own timer, `garden-notebooks-export.timer`,
|
|
every 30 min; own lock) scans for markers and runs each opted-in notebook
|
|
in a one-shot `marimo:latest` container — same image and `.env` as the
|
|
live server, so it hits the real databases. Output lands in
|
|
`notebooks-export/<slug>.html` plus an `index.json`.
|
|
- Cached on notebook content: a run after no edits does no work.
|
|
- Per-notebook timeout, and a failure keeps the previous export and
|
|
continues. A broken notebook can't take the garden down.
|
|
- It runs against a throwaway *copy* of the notebook dir, so notebooks
|
|
that write scratch files don't dirty the notebooks git repo.
|
|
2. `scripts/copy-notebooks.mjs` stages those into `public/nb/<slug>.html`.
|
|
3. Astro reads `index.json` (`src/lib/notebooks.mjs`) to build `/notebooks`
|
|
and `/notebooks/<slug>`, which wrap the raw export in an iframe with the
|
|
garden chrome, plus the sidebar section.
|
|
|
|
The raw export is at `/nb/<slug>.html`, deliberately *not* under
|
|
`/notebooks` — with `build.format: 'file'` the page and the raw file would
|
|
collide on the same URL.
|
|
|
|
URL shapes here are worth knowing, since `build.format: 'file'` makes the
|
|
listing a *file* (`dist/notebooks.html`) that sits beside a *directory* of
|
|
detail pages (`dist/notebooks/<slug>.html`). Verified against the running
|
|
nginx: `/notebooks` and `/notebooks/<slug>` resolve through the generic
|
|
`try_files`, but `/notebooks/` does not — a bare directory has no
|
|
`index.html`. `deploy/nginx.conf` carries an explicit `location = /notebooks/`
|
|
so the trailing-slash form works like every other folder URL on the site.
|
|
|
|
Both `notebooks-export/` and `public/nb/` are gitignored: tracking them would
|
|
make every export dirty the tree, which flips `auto-build.sh`'s change
|
|
signature and breaks the `git pull` at the top of each build tick. The
|
|
signature hashes `notebooks-export/` separately instead.
|
|
|
|
Force a full re-export with `deploy/export-notebooks.py --force`.
|
|
|
|
## Layout extras
|
|
|
|
Every page carries a left sidebar tree of the whole garden (folders
|
|
collapsible, current page highlighted; flows after the footer on narrow
|
|
screens) and the bandwidth odometer in the footer, fed by
|
|
`https://api.c0smere.net/bandwidth/odometer`. The widget animates at the
|
|
lifetime average rate from a fetched baseline — it never reflects live
|
|
throughput, and hides itself if the API is down.
|