Skip to content

Publishing the Documentation ​

The site you are reading is published by sync-docs.ps1 at the repository root — not by deploy.ps1.

powershell
.\sync-docs.ps1

That is the everyday case: build the VitePress site from your working tree, ship the static output to the server, and flip the live site to it atomically.

Not part of deploy.ps1, on purpose

deploy.ps1 does not publish documentation. Publishing docs must never be able to touch the database, the backend, or any container image, so it lives in its own script with its own permissions.

The one docs file deploy.ps1 does care about is docs/nginx.conf, because that is the only docs file baked into an image. Docs content deliberately does not appear in its change-detection list. Before that split, no docs/ path matched at all, so a docs commit deployed green and changed nothing.

Requires PowerShell 7 (#Requires -Version 7.0). PowerShell 5.1 mangles UTF-8 in this repository's scripts into what look like parse errors.

How it works ​

npm run docs:build  ->  tar  ->  scp  ->  releases/<stamp>/  ->  current

Docs content does not travel through git. The server keeps the last few releases under ~/apps/sdasis/docs-site/releases/, and current is a symlink that nginx resolves per request. So publishing needs no container restart, no image rebuild and no git push, and rolling back is a symlink flip.

Two details in that chain are load-bearing:

  • The symlink swap is atomic. It is ln -sfn … current.tmp && mv -T current.tmp current, never rm current && ln -s … — the latter opens a window in which every request 404s.
  • The symlink is relative. An absolute host path would dangle inside the container, which has no /home/rvyhmeister.

Why it builds locally instead of in the image ​

The docs image (docs/Dockerfile) is nginx:alpine and copies only nginx.conf. It serves content from the ./docs-site bind mount, and contains none itself.

It used to build the site in the image, which meant npm ci plus vitepress build on every deploy — and a fake git init to give VitePress something to read for lastUpdated, which stamped every page with the same build time. Building from the real working tree gives real per-file git dates instead.

Parameters ​

FlagEffect
(none)Build and publish
-DryRunPrint the plan, write nothing locally or remotely. Read-only probes still run, so the plan reflects real server state
-ListShow the releases on the server and which is live. Changes nothing
-RollbackPoint the live site back at the previous release
-To <stamp>Which release -Rollback should select
-SkipBuildPublish docs/.vitepress/dist as it already stands
-NoVerifySkip the post-publish HTTP check
-Keep <n>Releases to retain on the server. Default 5

Start with -DryRun, then -List if anything looks wrong.

Verification proves which release is live ​

Every release carries a .release-stamp marker, and the post-publish check fetches it back over HTTP. Without that, a 200 would only prove that something is being served — not that it is what you just uploaded. The check also fetches an asset, which catches a wrong base or a broken nginx alias.

You can check by hand at any time:

bash
curl https://sdasis.puc.edu/docs/.release-stamp

Serving ​

LayerConfiguration
Caddypath /docs /docs/* → reverse_proxy docs:80
nginx (docs/nginx.conf)location /docs/ → alias /srv/docs/current/; with try_files $uri $uri/ /docs/index.html, plus location = /docs { return 301 /docs/; }
Composethe docs service mounts ./docs-site:/srv/docs:ro
VitePressbase: '/docs/' in .vitepress/config.ts — this is what makes emitted asset URLs line up with the route and the alias

Two traps in the container healthcheck

It probes http://127.0.0.1/docs/, and both halves matter. / falls through to nginx's stock root and 404s, because the config only defines location /docs/. And localhost resolves to ::1 first while listen 80; binds IPv4 only. Either mistake alone makes the check fail forever — it once sat at FailingStreak 215.

It doubles as a content check: it fails if current is missing or dangling.

Caddyfile.local has no /docs block, so /docs/ cannot be exercised in the local :8443 harness.

Working on the docs locally ​

powershell
cd docs
npm install          # first time only
npm run docs:dev     # http://localhost:5174
npm run docs:build   # what sync-docs.ps1 runs
npm run docs:preview # serve the built output

The build fails on dead links. Only http://localhost URLs are exempted (ignoreDeadLinks), so a renamed page or a mistyped path is caught before it ships. Note that sidebar entries in .vitepress/config.ts are not dead-link checked — a sidebar item pointing at a page that does not exist builds green and 404s in the browser. Check new sidebar entries by clicking them in docs:dev.

Regenerating the ER diagram ​

The ER diagram is generated from the live database, not hand-maintained:

bash
python docs/generate_erd.py          # rewrite docs/er-diagram-10.md
python docs/generate_erd.py --check  # exit 3 if it is out of date
python docs/build_erd_page.py        # rebuild docs/public/er-diagram.html

The generator refuses to run if any table is in no section, in two sections, or in a section but not in the database — so a new migration forces a placement decision. See ER Diagram.

docs/er-split/ is a frozen revision-7 render and is intentionally not regenerated.

Regenerating is not the same as starting a revision. Re-running the generator after a migration rewrites the current file in place. A new revision gets a new file named after it (er-diagram-10.md holds revision 10): bump REVISION and DEFAULT_OUT in generate_erd.py and DEFAULT_IN in build_erd_page.py, rewrite build_changelog() for the migrations it covers, put a "Superseded" banner on the previous file, and repoint the sidebar (.vitepress/config.ts) and every /er-diagram-N link. Revision 8 is the exception that motivated the rule: it lives in er-diagram-7.md.

Student Information System Documentation