Files
mod/main.go
T
zooqueen 3b922935a2 retention and authority are the reasons; privacy was never one
The estate is going fully open source, so the private-module credential problem
is ending rather than being solved. Re-read against that, two reasons survive
and one part of the design does not.

Surviving, and neither depends on who may read a module:

  Authority — the forge answers for our module names, so a build does not need
  github.com to be up, willing, or still holding the repository.
  Retention  — one cache, nothing prunes it. hanzoai/s3-go is 404 on GitHub and
  alive on the forge; builds only kept working because a cache elsewhere
  happened to still hold it.

The 404 rule survives on a stronger argument than the one it was written for. It
was about a module path travelling to a public service. The real reason is that
404 means "ask the next proxy" and the next proxy resolves github.com/hanzoai/x
from github.com — so a 404 does not fail, it silently returns resolution to the
host this exists to make unnecessary, and the build goes GREEN having used it.
Behaviour unchanged; the test says why it is there.

Not surviving: exempting our whole namespace from the public transparency log.
That was privacy. The log is now asked about everything, ours included, and the
check is worth more for our modules than anyone else's — this is the one place
that reads the forge while the log remembers what was published under the same
name. UNLOGGED replaces it: an explicit list, EMPTY by default, naming only what
the log has never seen, shrinking to nothing as repositories are published.

GOPRIVATE is gone from the server. It was redundant beside an explicit per-request
GOPROXY, and it is the exact variable clients are told never to set. A test now
asserts it is never set.

FORGE_TOKEN becomes optional. Public forge repos are anonymously readable —
verified, not assumed — so empty is a working configuration. The URL rewrite
happens either way: reaching the forge and being allowed to read a given
repository there are different questions, and dropping the rewrite would send our
names back to github.com.

Canary the caller on ci v1.0.75, which stopped gover reading `go 1.26` as a
demand for Go 1.26.999 and refusing a builder that compiles it.
2026-08-19 22:50:18 -07:00

77 lines
2.2 KiB
Go

package main
import (
"log"
"net/http"
"os"
"path/filepath"
"strings"
)
// Configuration is read from the environment because every value here is
// deployment wiring — where the forge is, which namespaces we answer for — and
// none of it is a runtime policy anyone edits.
func main() {
forge := env("FORGE", "https://git.hanzo.ai")
token := os.Getenv("FORGE_TOKEN")
addr := env("ADDR", ":8080")
root := env("ROOT", "/var/lib/mod")
names := paths(env("NAMESPACES", "github.com/hanzoai"))
// Empty by default: the public checksum log is asked about everything unless
// a name is listed here, and a name belongs here only while the log has
// never seen it.
unlogged := paths(os.Getenv("UNLOGGED"))
dir := filepath.Join(root, "scratch")
if err := os.MkdirAll(dir, 0o700); err != nil {
log.Fatal(err)
}
// One scratch module gives the toolchain the module context its resolution
// commands need. It requires nothing and builds nothing.
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module scratch\n"), 0o600); err != nil {
log.Fatal(err)
}
p := &Proxy{
Namespaces: names,
Forge: strings.TrimSuffix(forge, "/"),
Upstream: env("UPSTREAM", "https://proxy.golang.org"),
Unlogged: unlogged,
Token: token,
Dir: dir,
Cache: filepath.Join(root, "cache"),
}
mux := http.NewServeMux()
mux.Handle("/", p)
// Liveness has to answer without touching the forge, or a forge blip
// restarts a proxy that is working.
mux.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte("ok\n"))
})
log.Printf("mod: serving %s from %s on %s", strings.Join(names, " "), p.Forge, addr)
log.Fatal(http.ListenAndServe(addr, mux))
}
func env(k, def string) string {
if v := os.Getenv(k); v != "" {
return v
}
return def
}
// paths splits a comma-separated list of module paths. An empty string is an
// empty list, not a list holding one empty path — and an empty path would match
// every module, which is the whole list's meaning inverted.
func paths(s string) []string {
var out []string
for _, p := range strings.Split(s, ",") {
if p = strings.Trim(strings.TrimSpace(p), "/"); p != "" {
out = append(out, p)
}
}
return out
}