Source for info.qcloud.systems, the QCS Trust & Status Center: upstream
service status, notices (incidents, maintenance, security advisories, service
changes), the roadmap, and QCS's policies. A Go program renders static HTML
into ./public, which GitHub Actions publishes to managed static hosting.
How the generator works is documented on the site itself, at
/how-its-built.html (source: templates/pages/how-its-built.html).
Everything this site serves is public by design. Agreements and executed client documents are not published from this repo.
The Trust & Status Center has one job: show that QCS is reliable and accountable. Its sections, in home-page order:
| Section | Source | Purpose |
|---|---|---|
| Overview | templates/pages/index.html |
What the site is, with links to status and notices |
| Status | statusServices() in content.go, static/js/status.js |
Live status of the upstream providers QCS depends on |
| Notices | notices/*.md |
Incident reports, maintenance, security advisories, service changes |
| What's New | CHANGELOG.MD (latest entry) |
Release highlights for this site |
| Roadmap | roadmap() in content.go |
In progress, planned, shipped |
| Policies | static/*.html, templates/pages/*.html |
Privacy, Security, Terms of Use, Accessibility |
| FAQ | faqs() in content.go |
Trust and status questions |
| How It's Built | templates/pages/how-its-built.html |
Public documentation of the Go generator |
These are easy to break by accident and expensive to get wrong. Read them before changing structure, URLs, or the engine.
- Never change the site address or the policy URLs.
/.well-known/security.txtpoints researchers at/security.html, and the policy pages (/privacy-policy.html,/terms-of-use.html,/security.html) may be linked from elsewhere. Renaming the subdomain or moving those files breaks those references. If a move is ever unavoidable, keep a page at the old URL that redirects to the new one. - Keep How It's Built accurate. That page documents the code in detail
and is meant to show the engine is understood. Any pull request that
changes behaviour must update
templates/pages/how-its-built.htmlin the same pull request. Check in particular:- the dependency count and approximate size (At a glance)
- the list of build-failing rules (Validation and safety), against
validate()inmain.go - the secret patterns (The notices engine, step 1), against
secretPatternsinnotices.go, including how many there are - the notice categories, against
noticeCategories()incontent.go - the tests table (Testing and CI), against
notices_test.go - the CI steps, against
.github/workflows/deploy.yml
- Everything here is public. The repository and the site are public. Do not add gated content, credentials, client documents, or internal details. Notices are scanned for credential-like strings and the build fails on a match, but the scanner cannot catch everything: read each notice before merging.
- No links to other QCS sites. This site stands alone. Links to the QCS GitHub organization and Medium profile are fine; links to other QCS websites, or the names of other QCS repositories, are not.
- Notices speak for QCS. They are published in the organization's name.
Nameinauthor()incontent.gois intentionally empty; setting it adds a personal byline and author box to every notice. - Deploy at least once a year.
security.txtgets a newExpiresdate (one year out) on every build; without a deploy it eventually expires.
make fmt vet # formatting and static analysis
make test # unit tests, including validation of the real content
make check # content validation only
make dev # preview at http://localhost:8080 (restart after adding a notice)CI runs go vet, go test and the build again on merge; a failure at any
step leaves the live site unchanged.
make dev # live server on http://localhost:8080
make build # render the site into ./public
make check # validate content without writing anything
make fmt vet # gofmt + go vetWithout make: go run . serve, go run . build, go run . check.
To run this alongside another local site that also uses :8080, give one of
them a different port:
go run . serve -addr :8081Add an entry to statusServices() in content.go with a Role (what the
provider does; this is the only label shown) and the Kind matching its feed
format:
kindAPIv2— the feed has/api/v2/status.jsonand/api/v2/incidents.json; set bothAPIandIncidents.kindSummary— the feed has a single/summary.json; leaveIncidentsempty.
go run . check enforces both. A provider whose feed does not allow
cross-origin requests cannot be added without a server-side proxy.
The daily bars are built from each provider's reported incidents, which is the only history these feeds expose. They track disclosure, not measured uptime.
The only dependency is Go. There is no Node toolchain.
main.go serve | build | check, plus the content validator
content.go site metadata, nav, roadmap, FAQ, notice categories, pages
notices.go the notices engine: notices/*.md -> /notices/ pages, with
front matter, categories, TOC, git dates, related
notices, search data, structured data, secret scanner
notices_test.go unit tests (go test ./... / make test)
notices/ Markdown notices, one file per notice
extras.go build-time extras: What's New, sitemap.xml, robots.txt,
.well-known/security.txt, notices RSS feed
CHANGELOG.MD latest entry feeds What's New; full log linked on GitHub
templates/
base.html shared chrome: sidebar, nav, meta tags, footer
pages/index.html the home page
pages/how-its-built.html documentation of this generator, published on the site
pages/notices.html the Notices index (search + category filter)
pages/notice.html a single notice (TOC, related notices)
pages/category.html one page per category, e.g. /notices/categories/incident.html
pages/security.html vulnerability disclosure policy
pages/accessibility.html accessibility statement
pages/404.html served by the host for any missing path
partials/ shared snippets, loaded with every page
static/ copied verbatim into ./public, "static/" stripped
css/site.css site styles
css/doc.css shared styles for the long documents
js/status.js live upstream status, fetched in the browser
js/notices.js copy buttons, heading links, search and filtering
og-image.png 1200x630 link-preview image
privacy-policy.html hand-written, kept as authored
terms-of-use.html hand-written, kept as authored
CONTRIBUTING.html hand-written, kept as authored
CNAME custom domain — must survive every deploy
favicon.ico
public/ BUILD OUTPUT — wiped on every build, gitignored
build also writes .nojekyll so Pages serves the output as-is.
| To change… | Edit |
|---|---|
| What's New, changelog | Add a ## [x.y.z] — date entry to CHANGELOG.MD |
| Roadmap | roadmap() in content.go |
| FAQ | faqs() in content.go |
| Notice categories | noticeCategories() in content.go |
| Notices | Add notices/short-name.md starting with # Title |
Notices are published on the next deploy. The file name becomes the URL, the
first # heading the title, and the first paragraph the summary on the
index. Every notice needs at least one category from noticeCategories()
(Incident, Maintenance, Security Advisory, Service Change); anything else
fails the build.
---
description: One sentence for search results and link previews.
categories: [Incident]
---
# API errors on 12 November
Between 14:05 and 14:40 UTC some requests failed. ...Published and updated dates come from git history automatically (the deploy
checks out full history for this); published: / updated: override them.
Each category gets its own page, notices sharing categories are cross-linked,
and every notice is listed in sitemap.xml and the RSS feed at
/notices/feed.xml.
Every notice is scanned for things that look like credentials (cloud keys,
tokens, private keys, password = …, passwords in URLs); a match fails the
build so it never reaches the public site. Raw HTML in a notice is escaped.
In make dev, edits to a notice show on refresh; a new notice needs a restart.
Notices are published in QCS's name. To credit an individual instead, set
Name in author() in content.go; that adds a byline and author box.
.well-known/security.txt gets a fresh Expires date (one year out) on
every build. Deploy at least once a year so it never expires.
- Palette: charcoal ink (
#1a2332), action blue (#0066cc), sky-blue accent (#6cc5e6) from the qcloud systems wordmark, on grey/white surfaces - Typography: Inter
- Layout: fixed sidebar on desktop, stacked on narrow screens
- QCS brand palette and type
- Create
templates/pages/yourpage.htmlwrapped in{{define "content"}} … {{end}}. - Add a
Pageentry topages()incontent.go.
go run . check fails on a missing template, a missing document, a project
without a URL, a missing CNAME, an unknown roadmap stage, an incomplete FAQ
entry, a changelog without a version heading, or a malformed analytics token.
Pushing to main triggers .github/workflows/deploy.yml: go vet,
go test, go run . build, then publish ./public to the static host, which
also provides HTTPS. Deployment needs no stored credentials.
static/CNAME holds the site's custom domain. The build copies it into the
output on every deploy, and the validator fails the build if it goes missing,
because losing it would silently drop the domain.
main only accepts changes through pull requests, so every change goes on a
branch:
git switch main && git pull # start from the latest main
git switch -c YYMMDDShortName # e.g. 261014PrivacyRepair
# ...edit, then run `make check` (and `make test` where available)...
git add -A
git commit -m "Short summary" -m "Longer explanation of what and why."
git push -u origin YYMMDDShortNameThen open the pull request and merge it, either on github.com (use the
Compare & pull request banner, then Merge pull request) or with
gh pr create --fill and gh pr merge --merge. Merging to main deploys.
Afterwards, bring your local copy up to date and delete the merged branch:
git switch main && git pull && git branch -d YYMMDDShortNameA codespace's built-in token can only access the repository the codespace was
created from. Cloning or pushing any other private repository fails with
403 Write access to repository not granted, and SSH URLs fail because a
codespace has no SSH key. Use HTTPS with your personal GitHub login instead.
Sign in once per codespace, if gh is not already logged in with your account:
env -u GITHUB_TOKEN gh auth login # GitHub.com → HTTPS → browser loginClone with credentials stored in the new clone, so later pull and push
work without extra steps:
cd /workspaces
git clone \
-c credential.helper= \
-c 'credential.helper=!env -u GITHUB_TOKEN gh auth git-credential' \
https://github.com/OWNER/REPO.gitFor a repository that was already cloned and now refuses to pull or push, run this once inside it:
git config credential.helper ''
git config --add credential.helper '!env -u GITHUB_TOKEN gh auth git-credential'In those repositories, prefix gh commands the same way, for example
env -u GITHUB_TOKEN gh pr create --fill.
Optional shortcut, saved to your shell profile in this codespace:
cat >> ~/.bashrc <<'BASHRC'
ghclone() {
cd /workspaces && git clone \
-c credential.helper= \
-c 'credential.helper=!env -u GITHUB_TOKEN gh auth git-credential' \
"https://github.com/$1.git" && cd "$(basename "$1")"
}
BASHRC
source ~/.bashrc
ghclone OWNER/REPOTo see several repositories side by side in the Explorer, use File → Add
Folder to Workspace…, or save a .code-workspace file in /workspaces
(outside every repository) and open it with File → Open Workspace from
File….
Typical usage (measured 2026-10-14 on a 2-core codespace):
| Item | Usage | Notes |
|---|---|---|
| A cloned repository | a few MB each | Clones are tiny next to everything else |
Tools and caches in ~ |
about 1.6 GB | Go module cache, CLI tools, editor settings |
| Codespace disk overall | about 12 GB of 32 GB | Mostly the base image: OS, languages, tools |
| Memory (RAM) | about 2–3 GB of 8 GB | Comes with the machine size; not billed separately |
Free monthly allowance on a GitHub Free personal account:
| Included | Counts when | |
|---|---|---|
| Compute | 120 core-hours (60 hours on a 2-core machine) | the codespace is running |
| Storage | 15 GB-month | the codespace exists, running or stopped |
One codespace kept for a whole month uses most of the storage allowance; a second codespace kept alongside it is what pushes usage over, not extra clones.
When GitHub charges: only if a Codespaces spending limit above $0 is set. With the default $0 limit, codespaces stop when the allowance runs out and can be used again when it resets at the start of the next month. Nothing is billed and files are kept.
Check actual usage: github.com/settings/billing, under Codespaces. The allowances above can change; GitHub's documentation page "About billing for GitHub Codespaces" has the current figures.
Keeping usage low:
- Stop the codespace when you finish (Command Palette → Codespaces: Stop Current Codespace) rather than closing the tab; idle codespaces keep running until they time out.
- Shorten the idle timeout to 10–15 minutes at github.com/settings/codespaces.
- Stay on a 2-core machine; larger machines use the allowance faster.
- Keep one codespace and clone extra repositories into it, rather than creating a codespace per repository.
- Push your work before deleting a codespace; anything not pushed is lost.
- Delete clones you no longer need once their work is pushed.
See CONTRIBUTING.html.
Fork, branch, test locally with make dev, open a pull request.
- 3.2.1 — Vendor-neutral site, docs and code
- 3.2.0 — Trust & Status Center: notices engine, How It's Built page
- 3.1.0 — Engineering Notes, roadmap, FAQ, articles, policy pages, SEO
- 3.0.0 — Go static site generator; agreements removed from this site
- 2.0.0 — Grey/blue redesign, GitHub Actions deployment
- 1.0.0 — Initial documentation hub
- Site: https://info.qcloud.systems
- GitHub: https://github.com/qcloud-systems
MIT. See the LICENSE file.