Extended core for the custom services of a self-hosted SourceHut instance (compare, spec, dolt, cover, bench, ...). Everything these services share that is ours — not upstream's — lives here, so the sr-ht-core fork can stay a clean mirror of upstream core-go, and so the services stop carrying drifting copies of the same code.
chrome — the shared page chrome: service-switcher nav built from the
shared config.ini (chrome.BuildNav), per-request chrome.Page with
login/logout/profile URLs against meta.sr.ht's unified login, embedded
srht-nav / srht-env-banner template partials (circle brand + red service
label + switcher + login box), and the generic template helpers (dict,
shortsha, reltime, abstime).grants — the grant vocabulary of tokens.sr.ht (SPEC ch. 3):
<service>:<action> members split on ASCII whitespace, * for every action
of every service, the reserved id:<n> member a registered token carries,
and the subset rule an exchange narrows by. Shared because the daemon that
mints and every service that validates have to read one grammar — two
parsers that disagree about what counts as a permission is a hole on the
security path, not a cosmetic divergence.bearer — the shared working-token validator (SPEC ch. 6), one copy for
every service that accepts a tokens.sr.ht token: verify the signature,
decide whether the token is ours, check the grant, and — only for a
registered token — ask tokens.sr.ht whether it is still live, behind a 60s
cache. Every step that can refuse locally runs before the one that cannot,
so a short token never touches the network at all.svc := chrome.NewService(conf, "compare.sr.ht")
svc.StyleHref = cssHref // after discovering the hashed stylesheet
t := chrome.MustAttach(template.New("layout").Funcs(chrome.Funcs()))
// ... parse the service's own templates into t ...
page := svc.Page(r, "Page title", username) // username "" = anonymous
In the layout:
{{template "srht-env-banner" .}}
<nav class="container navbar navbar-light navbar-expand-sm">
{{template "srht-nav" .}}
</nav>
The template dot must expose the chrome.Page fields — either a Page
itself, or a service view struct that embeds one (promoted fields resolve in
templates).
v, err := bearer.New(bearer.Options{
Origin: conf.Get("tokens.sr.ht", "origin"), // https://tokens.srht.bigb.es
ClientID: "bench.sr.ht", // the CALLING service
NodeID: hostname,
})
tok, err := v.Validate(r.Context(), presented, "bench:upload")
switch {
case err == nil:
// tok.Username, tok.Grants, tok.TokenID
case errors.Is(err, bearer.ErrNotOurs):
// service policy: accept as a meta.sr.ht PAT, or refuse
case errors.Is(err, bearer.ErrForbidden):
http.Error(w, "insufficient grants", http.StatusForbidden)
case errors.Is(err, bearer.ErrUnavailable):
http.Error(w, "token service unavailable", http.StatusServiceUnavailable)
default: // ErrInvalid, ErrRevoked
http.Error(w, "invalid token", http.StatusUnauthorized)
}
crypto.InitCrypto must have run first — the signing key and the network key
both live in that package's globals.
Two of those arms are the ones to get right. ErrNotOurs is deliberately not
decided by the validator: SPEC ch. 6 step 2 leaves it to each service whether a
foreign bearer token is a meta PAT to accept (dolt) or something to refuse
(bench, cover). And ErrUnavailable is 503, never 401 — reading an
unreachable daemon as "revoked" would refuse live tokens across the instance
for the length of a tokens.sr.ht restart.
The chrome bakes in the instance-wide decisions instead of parameterizing
them: the switcher renders only for authenticated viewers; paste, pages and
hub never appear in it; the brand is always circle + site name + red service
label, where the name links to hub (or to the service root on an instance
without one) and the label links to the service root; the profile link prefers
hub's ~username page when hub is configured. Service-specific nav entries go
through Service.ExtraNav; per-page width through Page.ContainerClass.
The brand is two links rather than upstream's one because upstream has to choose between them: with hub configured it points the whole brand at hub and drops the service label, without hub it keeps the label and points at the service root. Both halves are load-bearing — hub is excluded from the switcher, so the brand is the only route to it, and chrome that does not name its own service is worse chrome.
Note that embedding names the field Page: a view struct that wants Page
for its own payload (a pagination counter, usually) must rename that field.
The collision is a compile error, not a silent shadow.