Adding a news article or editing a page? See SITE_INSTRUCTIONS.md — the guide for updating the site entirely from the GitHub web interface — and start from the ready-made page templates in
templates/.
The source of the public website of the UNM Center for Advanced Research
Computing, https://carc.unm.edu/. Every page is a Markdown file in
docs/, structured as an
Open Knowledge Format (OKF) v0.2
knowledge bundle and rendered by scripts/build_cascade_site.py in the UNM
Cascade webcore standard. The site shares its design system and agent surface
with the user documentation served at
carc.unm.edu/docs/.
This repository is also the GitHub organization's root Pages site, so the
same build is mirrored at https://unm-carc.github.io/ — a staging copy
whose canonicals point at carc.unm.edu.
push to main
└─ GitHub Actions (.github/workflows/docs.yml)
├─ okf-conformance OKF validation + no hotlinked legacy assets
├─ build render, agent surface, .htaccess -> site/
├─ deploy-pages staging mirror at unm-carc.github.io
└─ publish-cascade scripts/cascade_sync.py -> Cascade CMS -> SFTP -> carc.unm.edu
The UNM web host that serves carc.unm.edu accepts SFTP only from campus,
so GitHub's runners cannot reach it. Cascade CMS can, and its REST API is
public: scripts/cascade_sync.py pushes the built tree into the Cascade site
carc.unm.edu V2 and Cascade publishes it. Cascade is a conduit, not an
editing surface — anything changed there by hand is overwritten by the next
publish.
publish-cascaderuns on every push tomainwhile the repository variableCASCADE_SYNC_ENABLEDistrue, and on demand from the Actions tab (mode: planis a dry run). It uses theCASCADE_API_KEYsecret in theunm-productionenvironment.- Ownership is a manifest stored in Cascade (
/_carc-sync/manifest-root.json, unpublished). The tool creates and edits what the build produces, deletes only assets it owns that the build no longer produces, never writes under the legacy folders, never folder-publishes a folder it did not create, and verifies every published file against the build by SHA-256. - Everything the rebuilt site does not own lives under
archive/in Cascade;_internal/(site machinery) and_carc-sync/(the manifests) stay at the root. Legacy pages that were never migrated keep serving at their old URLs from files already on the host — which meansarchive/must not be unpublished or deleted in Cascade until every page and image still worth keeping has been ported into this repository (seemigration/legacy-paths.txtfor the inventory anddocs/log.mdfor progress). Unpublishing it would remove ~150 pages and ~400 images at once.
pip install pyyaml markdown
python3 scripts/okf_validate.py docs # OKF conformance (CI-enforced)
python3 scripts/check_legacy_links.py docs # no hotlinks, every /assets/ path exists
python3 scripts/gen_llms_txt.py # llms.txt + llms-full.txt
python3 scripts/build_cascade_site.py # render -> site/
python3 scripts/postbuild_agent_surface.py site # Markdown mirror, OKF meta, robots.txt
python3 scripts/gen_htaccess.py site # 301s for superseded legacy URLs
python3 -m http.server 8080 -d site # preview at localhost:8080CARC_SITE_URL=https://example.test/ overrides site_url in zensical.toml
for a build without a commit (zensical.toml is only the site_url source;
Zensical does not render this site).
Publishing from a laptop instead of CI: python3 scripts/cascade_sync.py plan
shows what would change; sync --publish changed --verify-live applies it.
The key is read from $CASCADE_API_KEY or ~/.cascade/api_key.
Every page gets the UNM webcore chrome — header, section navigation,
breadcrumbs, footer and Quick Links panel (assets from webcore.unm.edu) —
around a plain content column. The homepage is hand-authored
(web/index.html; its self-links follow site_url at build time). A page
whose frontmatter says layout: cards renders its #### [Title](url) +
image + summary list as a card grid (research/featured-projects). The
Google Tag Manager container from the original site is carried into every
page, and ga4_id in zensical.toml adds CARC's own Google Analytics 4 tag
(leave it empty to disable).
- Every page has OKF frontmatter with a non-empty
type; sectionindex.mdfiles carry none;docs/log.mdis the dated changelog. - Images and documents live in
docs/assets/and are referenced as/assets/<name>; nothing hotlinks the old host, andscripts/check_legacy_links.pyfails the build if a page reintroduces one.migration/assets.ymlrecords where each migrated file came from. - Stories from the old site were ported by
scripts/port_legacy_pages.pyfrommigration/pages.yml(legacy path, new path, date, byline, oraction: skipwith the reason). News pages carry adate:key, and the Archive at the bottom ofdocs/news/index.md(betweenarchive:start/archive:endmarkers) is regenerated from it by that script. - Provenance is the redirect map. A page that records a
https://carc.unm.edu/...URL insources[].resourcegets a real Apache 301 from that URL, generated byscripts/gen_htaccess.py; hand-written cases go inmigration/redirects-extra.yml. The legacy.htaccessis kept for the record inmigration/htaccess-legacy.conf. - Editing conventions match the docs repo — see its AGENTS.md.