mirror of
https://github.com/hanzoai/mod.git
synced 2026-09-30 22:56:06 +00:00
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.
77 lines
2.2 KiB
Go
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
|
|
}
|