Reduces sites/quintodrome and sites/hyper to their genuinely site-specific
parts: a Config (branding, GitHub repo, Linux download-format list),
classifyAsset, and landing-page content/mockup. Everything else — the HTTP
server, routes, release cache, update-check protocol, and page chrome —
now lives once in internal/site and internal/component.
The Linux format toggle's button list, order, and per-format arch
availability is now driven entirely by Config.LinuxFormats, reproducing
each site's current behavior as data (quintodrome shows Snap on both
arches; hyper hides it on arm64) rather than as forked logic. The one
deliberate behavior change: platformLabel's "linux-*" normalization
(previously only applied to linux-rpm on hyper's side) now applies to all
linux-* formats, fixing a cosmetic label bug on hyper's deb/pacman/flatpak
download rows ("linux-deb · x64.deb" -> "Linux · x64.deb").
Also unifies top-level tooling: a single SITE-parameterized Makefile and
Dockerfile, a single tailwind.config.js scanning internal/** and sites/**,
and a shared public/ with both product logos.
Verified: go build/vet/test pass for both sites, and `make dev
SITE=quintodrome` / `SITE=hyper` were run and curl-checked against /,
/download (including each site's Linux format toggle and arch-filtering),
and /api/update-check, matching pre-refactor behavior.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4.5 KiB
quineglobal-sites
Monorepo for Quine Global's website + auto-update feed servers: currently Quintodrome (a native desktop app wrapping Navidrome) and Quine Hyper (a fork of the Hyper terminal). Both are the same server shape — release feed, download page, Hazel/Squirrel-style auto-update API — with only branding, asset-naming rules, and landing-page content differing per site.
Commands
All commands take SITE=quintodrome (default) or SITE=hyper.
make dev # start Go server + Tailwind watcher for SITE (Ctrl-C stops both)
make dev SITE=hyper # same, for the hyper site
make start # Go server only
make test # go test -shuffle on ./... (covers every site)
make css # one-shot Tailwind build (shared across sites)
make lint # golangci-lint
make build # Docker image for SITE (linux/amd64)
make push # build + push to forge.quinefoundation.com
Architecture
Pure Go HTTP server (go-chi/chi). HTML is rendered server-side with maragu.dev/gomponents (no templates). CSS is Tailwind, compiled to public/styles/app.css.
internal/site/ shared server framework: Server, routes, release cache, update-check protocol
internal/component/ shared gomponents chrome: page/nav/footer, download page, FeatureCard, DeviceWindow
sites/quintodrome/ Quintodrome's Config, classifyAsset, landing-page content, cmd/main.go
sites/hyper/ Quine Hyper's Config, classifyAsset, landing-page content, cmd/main.go
public/ static assets shared by all sites (images, compiled CSS)
scripts/ release refresh helper script (shared, identical across sites)
A site is defined entirely by sites/<site>/site.go's Config value
(internal/site.Config, embedding component.Branding and
component.DownloadConfig) plus that package's classifyAsset and
HomeContent. Everything else — HTTP routing, the release cache, the
update-check handler, the download page's mechanics — is shared code in
internal/site and internal/component.
Key concepts
Release cache (internal/site/releases.go): fetches GitHub releases from Config.RepoOwner/Config.RepoName via the GitHub API, caches them in memory per-server. TryRefresh is debounced (called per-request, no-ops if cache is fresh or a fetch is already running). ForceRefresh is used by the internal /internal/refresh endpoint.
Asset classification (sites/<site>/assets.go): each site's own classifyAsset maps GitHub release asset filenames to (os, arch) keys used in Release.Assets, since real release artifact naming conventions differ per product (this is intentionally not shared logic). Supported OS keys: mac, windows, linux (AppImage), linux-rpm, linux-deb, linux-snap, linux-pacman, linux-flatpak. armv7l and other unrecognised ARM variants are explicitly excluded (return ok=false) rather than misclassified as x64.
Auto-update feed (internal/site/updatecheck.go): implements a Hazel/Squirrel-style update protocol, shared by all sites. Responds to GET /api/update-check and GET /update with 204 (no update) or 200 JSON {name, notes, pub_date, url}. Handles stable and canary tracks; platform strings from the client (darwin, win32, deb, etc.) are normalised via normalizePlatformForAssets.
Download page (internal/site/home.go, internal/component/download.go): detects platform from User-Agent (Linux default comes from Config.DefaultLinuxOS), supports manual override via ?os= and ?arch= query params. The Linux format toggle's button list, order, and per-format architecture availability are entirely driven by Config.LinuxFormats — e.g. Quintodrome offers Flatpak first and ships Snap for both arm64/x64, while Hyper offers AppImage first and only ships Snap for x64 (hidden on arm64 automatically since the config declares it x64-only).
Testing
Tests live alongside source: package site/site_test in internal/site, package quintodrome/hyper in each site dir. Run with make test. Notable test files:
sites/<site>/assets_test.go— per-siteclassifyAssetcasesinternal/site/releases_test.go— sharedparseTaginternal/site/updatecheck_test.go— sharedsemverCmp,newerCanary,normalizePlatformForAssetsinternal/site/server_test.go— integration test that starts a real server against a minimal testConfig