Source for the community website of rustguac, a lightweight Rust replacement for the Apache Guacamole Java webapp: browser-based SSH, RDP, VNC, SPICE, Proxmox VE consoles, web browsing and VDI desktops through guacd.
This repository holds the website only. rustguac's code, releases, issues and pull requests live in sol1/rustguac.
The site is plain HTML, CSS and a little vanilla JavaScript, generated by zero-dependency Node
scripts. No framework, no npm install, no analytics, no trackers, no CDNs and no third-party
requests: fonts, icons and search are all self-hosted.
npm run dev # build, serve http://127.0.0.1:8080 and rebuild + live-reload on every change
npm run build # → dist/
npm run check # build, then the QA gate (SEO, links, structured data, CSP, privacy, search) and html-validate
npm run serve # build, then serve dist/ like GitHub Pages at http://127.0.0.1:8080Requires Node 20+ and nothing else: there is no npm install. npm run check fetches
html-validate through npx.
npm run dev (scripts/dev.mjs) watches src/, content/ and scripts/, rebuilds dist/ on
every save, holds requests while a build runs and reloads open tabs through a small server-sent
events script that only the dev server injects. PORT, HOST and SITE_URL work as below.
The site works at a domain root and under a project subpath (GitHub Pages project sites).
SITE_URL sets both the absolute URL and the base path:
npm run check:base # build for http://127.0.0.1:8080/rustguac-website, then check
npm run serve:base # same build, served at http://127.0.0.1:8080/rustguac-website/scripts/check.mjs and scripts/serve.mjs read the base path from the built dist/, so they always
match the build you made. DIST=<dir> sends a build somewhere other than dist/ (and points check
and serve at it), which is handy while npm run dev owns dist/:
DIST=/tmp/rg SITE_URL=http://127.0.0.1:9000 node scripts/build.mjs
DIST=/tmp/rg node scripts/check.mjs && DIST=/tmp/rg PORT=9000 node scripts/serve.mjssrc/
site.mjs facts used everywhere: version (from the docs sync), links, nav, href() and base path
content.mjs copy: highlights, session types, feature catalog, comparison, FAQs, quickstart
pages/ one module per page (home, features, guacamole-alternative, faq, 404, search)
docs/ docs pages built from content/docs/*.md (sidebar order in nav.mjs)
lib/ zero-dependency Markdown renderer and syntax highlighter for the docs
search/ build-time search indexer
layout.mjs <head>, SEO + Open Graph, JSON-LD, CSP, header, footer, screenshot helpers
icons.mjs inline Lucide-style SVG icons and the rustguac mascot
styles/ CSS, concatenated in filename order (00-tokens.css holds every design token)
js/ main.js (theme, menu, tabs, copy, lightbox), docs.js, search.js
static/ copied verbatim into dist/ (fonts, icons, generated images)
content/docs/ Markdown synced from rustguac's docs/ (npm run sync-docs); never edited by hand
content/docs-overlay/ fixes.mjs (site-side fixes tracked by upstream issue) and new pages not upstream yet
media/ sources: app screenshots (+ SHOTS.md captions and alt text), brand marks, fonts
scripts/
build.mjs renders pages, search index, sitemap.xml, robots.txt, llms.txt, llms-full.txt,
manifest, security.txt, .nojekyll and CNAME
check.mjs static QA gate used by CI and the Pages workflow
build-media.mjs screenshots → WebP, favicons and PWA icons, wordmarks, 1200×630 social cards
build-fonts.py media/fonts → subset woff2
serve.mjs tiny static server that mounts dist/ at the build's base path
dev.mjs dev server: rebuild on change + live reload (never used in CI)
sync-docs.mjs re-sync content/docs from a rustguac checkout or GitHub tag
Production is https://sol1.github.io/rustguac-website/, served by GitHub Pages with Source
set to GitHub Actions. Every push to main runs .github/workflows/pages.yml: it asks
actions/configure-pages for the site URL, builds with SITE_URL set to it, runs the checks and
deploys dist/. Run the Deploy to GitHub Pages workflow by hand to redeploy without a push.
Every link and asset carries the /rustguac-website base path.
Custom domain. None is set. To serve the site from one, add a repository variable named
CNAME_DOMAIN (Settings → Secrets and variables → Actions → Variables) with the domain, point
its DNS at GitHub Pages, and enter the same domain under Settings → Pages → Custom domain. The
build then writes dist/CNAME, and configure-pages reports the domain as the base URL, so links
are built for the domain root.
Pull requests run .github/workflows/ci.yml: build, checks, html-validate and an XML check of the
sitemap, both for production and at a domain root.
New rustguac release. The Sync rustguac release workflow (.github/workflows/release-sync.yml)
checks sol1/rustguac every hour. When a new release is out, it syncs the docs from that tag, runs the
same checks as CI, commits the sync to main and deploys the site. Nobody has to merge anything; if
the checks fail, nothing is pushed and a "Release sync failed" issue is opened instead.
The version, release date, release links, JSON-LD softwareVersion, llms.txt, docs "Updated" dates
and security.txt expiry all come from content/docs/_source.json, so the sync alone keeps them
current. To sync by hand, run the workflow with a tag, or run npm run sync-docs -- --ref vX.Y.Z locally.
What the sync can't do is rewrite copy. Each sync adds that release's checklist to one open Content review issue, so releases pile up there until the next content refresh:
- Update copy in
src/content.mjsfor new or changed features, and setreleasethere to the new release's headline (the home page pill). Until it matches, the pill just reads "Release notes". - Remove overlay fixes whose upstream issue is closed and fixed in a synced release. The issue shows each overlay issue's state, and the build warns about fixes that no longer change anything.
- Refresh the screenshots if the UI changed.
Close the issue after the refresh; the next sync opens a new one.
Docs sync. content/docs/ is a copy of rustguac's docs/*.md, with non-example sample data
swapped for reserved example data, plus _source.json (repo, ref, commit, version, release date
and per-file dates used for "Updated" and the sitemap's lastmod). Every docs page links back to
its source in sol1/rustguac ("Edit this page on GitHub", "View source"), so fixes go upstream:
npm run sync-docs -- --ref v1.12.1 # download that tag from GitHub (gh, else curl)
npm run sync-docs -- ../rustguac --ref v1.12.1 # copy from a local checkout, record the tag's commitCross-doc links become site links, links to other repo files point at GitHub at the synced ref, and heading ids follow GitHub's slugs so upstream anchors keep working. The build warns about anchors that don't resolve.
Docs overlay. Problems in the upstream docs are fixed upstream first: open an issue on
sol1/rustguac, then, if the site shouldn't wait, add an entry to content/docs-overlay/fixes.mjs:
{ issue: 303, file: "installation.md", title: "Post-install steps numbered 1 to 6", apply: (md) => md.replace(...) }apply runs on the synced markdown at build time. When a sync brings in the upstream fix, apply
no longer changes anything and the build prints overlay fix for #303 ... no longer applies: delete the
entry. A page upstream doesn't have yet lives next to it as content/docs-overlay/<slug>.md, with an
issue on its entry in src/docs/nav.mjs; its "Edit" link points at that issue. Once upstream ships
docs/<slug>.md, the synced copy wins and the build tells you to delete the overlay file.
Copy. Edit src/content.mjs or the page module. Keep claims factual and backed by rustguac's
README, docs or release notes. FAQ answers should stand on their own: answer engines quote them out
of context. Write internal links as /docs/...; they are prefixed with the base path at build time.
Screenshots. Put PNGs in media/screenshots/{dark,light}/ (dark = rustguac's aurora theme,
light = its light theme; 1440 px wide, editors 1440×1800) and describe them in
media/screenshots/SHOTS.md. Use only example data (example.com, *.example.internal, the
192.0.2.0/24, 198.51.100.0/24 and 203.0.113.0/24 documentation ranges). Then regenerate the derived
images. Needs ImageMagick 7, cwebp, oxipng and Chrome or Chromium:
npm run media # everything: screenshots, icons, og cards, README wordmarks
node scripts/build-media.mjs shots og # or just some of themEach screenshot becomes stripped WebP at 1440, 960, 720 and 640 px in src/static/assets/img/shots/,
listed in src/shots.json (dimensions for width/height, so no layout shift). Pages emit both theme
variants as lazy images and CSS shows the one matching the site theme, so only one is downloaded. Any
expected screenshot that is missing gets a labelled placeholder so the build never breaks. The
-mobile captures are kept as sources only: rustguac has no mobile layout yet (sol1/rustguac#306), so
the site doesn't show them. Generated files are committed, so a normal build never needs those tools.
Fonts. src/static/assets/fonts/ holds subset Inter and JetBrains Mono (from media/fonts/).
Regenerate with python3 scripts/build-fonts.py (needs pip install fonttools brotli).
New page. Add a module under src/pages/, register it in src/pages/index.mjs, add an og
block and run node scripts/build-media.mjs og. New docs pages come from markdown instead: see
the docs overlay above. It joins the sitemap, llms.txt, the search index
and the checks automatically.
- Unique title and description per page, canonical URLs,
max-image-preview:large. - One JSON-LD graph per page:
Organization,WebSitewith aSearchAction,WebPage,BreadcrumbList,SoftwareApplication+SoftwareSourceCodeon the home page,FAQPageon/faq/only andTechArticleon docs pages. sitemap.xmlwithlastmodand image entries (docs included), androbots.txtthat welcomes AI crawlers by name.llms.txt(a link-rich summary) andllms-full.txt(the full text of every doc).- Per-page 1200×630 social cards, full Open Graph and Twitter tags.
No analytics, cookies or third-party requests. A strict Content Security Policy is set per page
(the only inline script, the theme boot script, is allowed by hash). check.mjs fails the build on
third-party resources, mixed content, tracker IDs and email addresses outside the RFC 2606 example
domains.
Issues and pull requests are welcome. Please run npm run check before opening a PR. For rustguac
itself, head to sol1/rustguac.
Website code: MIT. Fonts: Inter and JetBrains Mono under the SIL Open Font License
(see src/static/assets/fonts/). rustguac is licensed under Apache-2.0 and developed by Sol1 and
contributors; its name, mascot and screenshots belong to the rustguac project. This is an independent
community website, not affiliated with or endorsed by the Apache Software Foundation. Apache, Apache
Guacamole and Guacamole are trademarks of the Apache Software Foundation.