package main import ( "bytes" "encoding/json" "net" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "sync/atomic" "testing" ) // controlTestEnv redirects every path the creation core touches into temp dirs // and replaces systemd with a fake that binds the instance's unix socket itself // (serving the health check and the setup-invite mint), so addInstance runs its // real state machine — env files, registry, symlinks, health checks — with no // systemd and no /data. func controlTestEnv(t *testing.T) *Registry { t.Helper() oldData, oldSock, oldOpt, oldSelf, oldSysctl, oldDisk, oldReady := instanceDataDir, instanceSocketDir, optDir, selfPath, systemctl, freeDiskBytes, confirmPublicReady t.Cleanup(func() { instanceDataDir, instanceSocketDir, optDir, selfPath, systemctl, freeDiskBytes, confirmPublicReady = oldData, oldSock, oldOpt, oldSelf, oldSysctl, oldDisk, oldReady }) confirmPublicReady = func(string, string) {} // no router to dial in tests instanceDataDir = t.TempDir() optDir = t.TempDir() // The socket dir must stay under the ~104-byte sun_path limit, and t.TempDir // embeds the test name — use a plain short temp dir instead. sockDir, err := os.MkdirTemp("", "lcs") if err != nil { t.Fatal(err) } t.Cleanup(func() { os.RemoveAll(sockDir) }) instanceSocketDir = sockDir // Stage a stub file, not the multi-MB test binary. stub := filepath.Join(t.TempDir(), "stub") if err := os.WriteFile(stub, []byte("#!/bin/sh\n"), 0o755); err != nil { t.Fatal(err) } selfPath = func() (string, error) { return stub, nil } freeDiskBytes = func(string) (uint64, error) { return 100 << 30, nil } // The systemd stand-in: "enable --now lchat@" binds the instance socket // and answers like a healthy instance; everything else is a no-op. systemctl = func(args ...string) error { if len(args) == 3 && args[0] == "enable" && args[1] == "--now" && strings.HasPrefix(args[2], "lchat@") { host := strings.TrimPrefix(args[2], "lchat@") ln, err := net.Listen("unix", filepath.Join(instanceSocketDir, host+".sock")) if err != nil { return err } mux := http.NewServeMux() mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) }) mux.HandleFunc("POST /api/admin/key-invites", func(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]any{"ok": true}) }) srv := &http.Server{Handler: mux} go srv.Serve(ln) t.Cleanup(func() { srv.Close() }) } return nil } reg, err := openRegistry(filepath.Join(t.TempDir(), "registry.db")) if err != nil { t.Fatal(err) } return reg } func newTestControld(t *testing.T, reg *Registry, maxInstances int) (*httptest.Server, string) { t.Helper() token := "test-control-token" c := &controld{reg: reg, tokenHash: hashToken(token), maxInstances: maxInstances, minFreeGB: 5} srv := httptest.NewServer(c.handler()) t.Cleanup(srv.Close) return srv, token } func controlCreate(t *testing.T, srv *httptest.Server, token, host string) *http.Response { t.Helper() body, _ := json.Marshal(map[string]string{"host": host}) req, _ := http.NewRequest("POST", srv.URL+"/instances", bytes.NewReader(body)) if token != "" { req.Header.Set("Authorization", "Bearer "+token) } resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } t.Cleanup(func() { resp.Body.Close() }) return resp } func decodeBody(t *testing.T, resp *http.Response) map[string]any { t.Helper() var m map[string]any if err := json.NewDecoder(resp.Body).Decode(&m); err != nil { t.Fatalf("decode body: %v", err) } return m } // wakeOnProbe replaces confirmPublicReady with a fake first wake: it binds the // host's socket and answers like a freshly spawned child (health check + the // setup-invite mint) — which is exactly what the router's activator does when // the create-path readiness probe hits a hibernating host. controlTestEnv's // cleanup restores the real confirmPublicReady. func wakeOnProbe(t *testing.T) { t.Helper() confirmPublicReady = func(h, _ string) { ln, err := net.Listen("unix", filepath.Join(instanceSocketDir, h+".sock")) if err != nil { t.Errorf("bind woken socket: %v", err) return } mux := http.NewServeMux() mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) }) mux.HandleFunc("POST /api/admin/key-invites", func(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]any{"ok": true}) }) srv := &http.Server{Handler: mux} go srv.Serve(ln) t.Cleanup(func() { srv.Close() }) } } // socketTripwire binds a host's instance socket and counts connections — proof // that a code path stayed a pure metadata read (dialed no socket, woke nothing). func socketTripwire(t *testing.T, host string) *atomic.Int32 { t.Helper() var dials atomic.Int32 ln, err := net.Listen("unix", filepath.Join(instanceSocketDir, host+".sock")) if err != nil { t.Fatal(err) } t.Cleanup(func() { ln.Close() }) go func() { for { c, err := ln.Accept() if err != nil { return } dials.Add(1) c.Close() } }() return &dials } // The replication endpoint: records the class on the registry route and // reloads the router exactly once per actual change — a matching class is a // no-op with no reload (the cloud sweep re-pushes every pass), and garbage // classes / unknown hosts never touch anything. func TestControldReplicationEndpoint(t *testing.T) { reg := controlTestEnv(t) if err := reg.upsert(Route{Host: "space.11ch.at", Kind: "instance", Socket: "/run/s.sock", Hibernate: true}); err != nil { t.Fatal(err) } if err := reg.upsert(Route{Host: "svc.11ch.at", Kind: "service", Socket: "/run/v.sock"}); err != nil { t.Fatal(err) } srv, token := newTestControld(t, reg, 40) var reloads int systemctl = func(args ...string) error { if len(args) == 2 && args[0] == "reload" { reloads++ } return nil } post := func(host, class, tok string) *http.Response { t.Helper() body, _ := json.Marshal(map[string]string{"class": class}) req, _ := http.NewRequest("POST", srv.URL+"/instances/"+host+"/replication", bytes.NewReader(body)) if tok != "" { req.Header.Set("Authorization", "Bearer "+tok) } resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } t.Cleanup(func() { resp.Body.Close() }) return resp } if resp := post("space.11ch.at", "daily", ""); resp.StatusCode != http.StatusUnauthorized { t.Errorf("no token: status = %d, want 401", resp.StatusCode) } if resp := post("space.11ch.at", "hourly", token); resp.StatusCode != http.StatusBadRequest { t.Errorf("bad class: status = %d, want 400", resp.StatusCode) } if resp := post("ghost.11ch.at", "daily", token); resp.StatusCode != http.StatusNotFound { t.Errorf("unknown host: status = %d, want 404", resp.StatusCode) } if resp := post("svc.11ch.at", "daily", token); resp.StatusCode != http.StatusNotFound { t.Errorf("service route: status = %d, want 404", resp.StatusCode) } if rt, _ := reg.get("space.11ch.at"); rt.Replication != "" || reloads != 0 { t.Fatalf("rejected requests left marks: class=%q reloads=%d", rt.Replication, reloads) } resp := post("space.11ch.at", "daily", token) if resp.StatusCode != http.StatusOK { t.Fatalf("set: status = %d, want 200", resp.StatusCode) } if body := decodeBody(t, resp); body["changed"] != true { t.Errorf("set: changed = %v, want true", body["changed"]) } if rt, _ := reg.get("space.11ch.at"); rt.Replication != "daily" { t.Fatalf("class not recorded: %q", rt.Replication) } if reloads != 1 { t.Fatalf("reloads after set = %d, want 1", reloads) } // Idempotent re-push (the sweep's steady state): no change, no reload. resp = post("space.11ch.at", "daily", token) if resp.StatusCode != http.StatusOK { t.Fatalf("re-push: status = %d, want 200", resp.StatusCode) } if body := decodeBody(t, resp); body["changed"] != false { t.Errorf("re-push: changed = %v, want false", body["changed"]) } if reloads != 1 { t.Fatalf("re-push reloaded the router: %d reloads", reloads) } } // A create request carrying a replication class produces a route born on that // rung (what the provisioner sends for a fresh free space). func TestControldCreateBornClassed(t *testing.T) { reg := controlTestEnv(t) srv, token := newTestControld(t, reg, 40) body, _ := json.Marshal(map[string]string{"host": "born.11ch.at", "replication": "daily"}) req, _ := http.NewRequest("POST", srv.URL+"/instances", bytes.NewReader(body)) req.Header.Set("Authorization", "Bearer "+token) resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != http.StatusCreated { t.Fatalf("create: status = %d, want 201", resp.StatusCode) } rt, err := reg.get("born.11ch.at") if err != nil || rt == nil { t.Fatalf("route: %+v, %v", rt, err) } if rt.Replication != "daily" { t.Errorf("born class = %q, want daily", rt.Replication) } } // A create carrying home_region records it on the route (multi-edge Phase 1): // the field crosses the socket in controlCreateReq → addInstanceOpts.HomeRegion // → the registry row the mesh later places by (mesh.candidatesFor). The value is // trimmed but not otherwise touched; omitted leaves the space region-agnostic. func TestControldCreateStampsHomeRegion(t *testing.T) { reg := controlTestEnv(t) srv, token := newTestControld(t, reg, 40) create := func(host, region string) { t.Helper() payload := map[string]string{"host": host} if region != "" { payload["home_region"] = region } body, _ := json.Marshal(payload) req, _ := http.NewRequest("POST", srv.URL+"/instances", bytes.NewReader(body)) req.Header.Set("Authorization", "Bearer "+token) resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != http.StatusCreated { t.Fatalf("create %s: status = %d, want 201", host, resp.StatusCode) } } create("ie-space.11ch.at", " ie ") // trimmed crossing the boundary if rt, _ := reg.get("ie-space.11ch.at"); rt == nil || rt.HomeRegion != "ie" { t.Fatalf("route = %+v, want home_region=ie", rt) } // Omitted ⇒ region-agnostic, exactly today's behaviour (home_region ""). create("agnostic.11ch.at", "") if rt, _ := reg.get("agnostic.11ch.at"); rt == nil || rt.HomeRegion != "" { t.Fatalf("route = %+v, want empty home_region", rt) } } func TestControldRequiresToken(t *testing.T) { reg := controlTestEnv(t) srv, _ := newTestControld(t, reg, 40) if resp := controlCreate(t, srv, "", "a.example.com"); resp.StatusCode != http.StatusUnauthorized { t.Errorf("no token: status = %d, want 401", resp.StatusCode) } if resp := controlCreate(t, srv, "wrong", "a.example.com"); resp.StatusCode != http.StatusUnauthorized { t.Errorf("bad token: status = %d, want 401", resp.StatusCode) } } func TestControldCreatesInstance(t *testing.T) { reg := controlTestEnv(t) srv, token := newTestControld(t, reg, 40) resp := controlCreate(t, srv, token, "new.example.com") if resp.StatusCode != http.StatusCreated { t.Fatalf("status = %d, want 201", resp.StatusCode) } m := decodeBody(t, resp) if m["host"] != "new.example.com" || m["url"] != "https://new.example.com" { t.Errorf("host/url = %v/%v", m["host"], m["url"]) } setup, _ := m["setup_link"].(string) if !strings.HasPrefix(setup, "https://new.example.com/join/setup#") { t.Errorf("setup_link = %q", setup) } admin, _ := m["admin_link"].(string) if !strings.HasPrefix(admin, "https://new.example.com/admin#token=") { t.Errorf("admin_link = %q", admin) } // The route is registered ready and the env file holds the same token. rt, err := reg.get("new.example.com") if err != nil || rt == nil || rt.Status != "ready" || rt.Kind != "instance" { t.Fatalf("route = %+v, err %v", rt, err) } env, err := readEnvFile(filepath.Join(instanceDataDir, "new.example.com.env")) if err != nil { t.Fatal(err) } if !strings.HasSuffix(admin, env["LCHAT_ADMIN_TOKEN"]) { t.Error("admin_link token does not match the env file") } } func TestControldConfirmsPublicReady(t *testing.T) { reg := controlTestEnv(t) var confirmed string confirmPublicReady = func(host, addr string) { confirmed = host } srv, token := newTestControld(t, reg, 40) if resp := controlCreate(t, srv, token, "ready.example.com"); resp.StatusCode != http.StatusCreated { t.Fatalf("status = %d, want 201", resp.StatusCode) } // Readiness through the public path is confirmed for the new host before the // setup link is returned, so a click lands on a ready admin sign-on. if confirmed != "ready.example.com" { t.Errorf("confirmPublicReady called with %q, want ready.example.com", confirmed) } } func TestControldAtCapacity(t *testing.T) { reg := controlTestEnv(t) srv, token := newTestControld(t, reg, 1) if err := reg.upsert(Route{Host: "full.example.com", Kind: "instance", Socket: "/x.sock", Status: "ready"}); err != nil { t.Fatal(err) } resp := controlCreate(t, srv, token, "new.example.com") if resp.StatusCode != http.StatusServiceUnavailable { t.Fatalf("status = %d, want 503", resp.StatusCode) } if m := decodeBody(t, resp); m["error"] != "at_capacity" { t.Errorf("error = %v, want at_capacity", m["error"]) } } func TestControldServiceRoutesDontCountTowardCapacity(t *testing.T) { reg := controlTestEnv(t) srv, token := newTestControld(t, reg, 1) // A 'service' route (home, push, cloud) must not eat instance capacity. if err := reg.upsert(Route{Host: "home.example.com", Kind: "service", Socket: "/h.sock", Status: "ready"}); err != nil { t.Fatal(err) } if resp := controlCreate(t, srv, token, "new.example.com"); resp.StatusCode != http.StatusCreated { t.Errorf("status = %d, want 201", resp.StatusCode) } } func TestControldDiskFloor(t *testing.T) { reg := controlTestEnv(t) srv, token := newTestControld(t, reg, 40) freeDiskBytes = func(string) (uint64, error) { return 1 << 30, nil } // 1 GB free, floor is 5 resp := controlCreate(t, srv, token, "new.example.com") if resp.StatusCode != http.StatusServiceUnavailable { t.Fatalf("status = %d, want 503", resp.StatusCode) } if m := decodeBody(t, resp); m["error"] != "at_capacity" { t.Errorf("error = %v, want at_capacity", m["error"]) } } func TestControldHostExists(t *testing.T) { reg := controlTestEnv(t) srv, token := newTestControld(t, reg, 40) if err := reg.upsert(Route{Host: "taken.example.com", Kind: "instance", Socket: "/x.sock", Status: "ready"}); err != nil { t.Fatal(err) } resp := controlCreate(t, srv, token, "taken.example.com") if resp.StatusCode != http.StatusConflict { t.Fatalf("status = %d, want 409", resp.StatusCode) } if m := decodeBody(t, resp); m["error"] != "host_exists" { t.Errorf("error = %v, want host_exists", m["error"]) } } func TestControldInvalidHost(t *testing.T) { reg := controlTestEnv(t) srv, token := newTestControld(t, reg, 40) resp := controlCreate(t, srv, token, "Bad Host!") if resp.StatusCode != http.StatusBadRequest { t.Fatalf("status = %d, want 400", resp.StatusCode) } if m := decodeBody(t, resp); m["error"] != "invalid_host" { t.Errorf("error = %v, want invalid_host", m["error"]) } } func TestControldDeleteInstance(t *testing.T) { reg := controlTestEnv(t) srv, token := newTestControld(t, reg, 40) // Create a real (fake-systemd) instance first. if resp := controlCreate(t, srv, token, "gone.example.com"); resp.StatusCode != http.StatusCreated { t.Fatalf("create: %d", resp.StatusCode) } del := func(tok, host string) *http.Response { req, _ := http.NewRequest("DELETE", srv.URL+"/instances/"+host, nil) if tok != "" { req.Header.Set("Authorization", "Bearer "+tok) } resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } t.Cleanup(func() { resp.Body.Close() }) return resp } if r := del("", "gone.example.com"); r.StatusCode != http.StatusUnauthorized { t.Fatalf("no token: %d, want 401", r.StatusCode) } if r := del(token, "never-was.example.com"); r.StatusCode != http.StatusNotFound { t.Fatalf("unknown host: %d, want 404", r.StatusCode) } if r := del(token, "gone.example.com"); r.StatusCode != http.StatusOK { t.Fatalf("delete: %d, want 200", r.StatusCode) } // The route is gone… if rt, _ := reg.get("gone.example.com"); rt != nil { t.Errorf("route still present after delete: %+v", rt) } // …the live data files are gone from /data… if _, err := os.Stat(filepath.Join(instanceDataDir, "gone.example.com.env")); !os.IsNotExist(err) { t.Error("env file still in /data — should be archived") } // …but ARCHIVED into the trash, not destroyed (the operator safety net). matches, err := filepath.Glob(filepath.Join(instanceDataDir, "trash", "gone.example.com-*", "gone.example.com.env")) if err != nil || len(matches) != 1 { t.Errorf("archived env not found in trash (matches=%v err=%v)", matches, err) } } // ---- hibernate-by-default creates (#550) --------------------------------------- // With LCHAT_CONTROL_HIBERNATE_CREATES on, a provisioner create is born // hibernating: routed + env + staged binary, setup link minted through the // probe-triggered first wake — but no systemd unit and no DB (the wake boots // fresh). func TestControldHibernateCreates(t *testing.T) { reg := controlTestEnv(t) // Pure recorder: the hibernating path must never enable a unit, so nothing // here binds the socket for it — only the fake wake below does. var calls []string systemctl = func(args ...string) error { calls = append(calls, strings.Join(args, " ")) return nil } wakeOnProbe(t) token := "test-control-token" c := &controld{reg: reg, tokenHash: hashToken(token), maxInstances: 40, minFreeGB: 5, hibernateCreates: true} srv := httptest.NewServer(c.handler()) t.Cleanup(srv.Close) resp := controlCreate(t, srv, token, "hib.11ch.at") if resp.StatusCode != http.StatusCreated { t.Fatalf("status = %d, want 201", resp.StatusCode) } m := decodeBody(t, resp) if setup, _ := m["setup_link"].(string); !strings.HasPrefix(setup, "https://hib.11ch.at/join/setup#") { t.Errorf("setup_link = %q — the probe-woken instance should have minted it", setup) } rt, err := reg.get("hib.11ch.at") if err != nil || rt == nil || rt.Status != "ready" || !rt.Hibernate { t.Fatalf("route = %+v, err %v; want a ready hibernating instance", rt, err) } for _, call := range calls { if strings.HasPrefix(call, "enable ") { t.Errorf("a systemd unit was touched: %q", call) } } if _, err := os.Stat(filepath.Join(instanceDataDir, "hib.11ch.at.db")); !os.IsNotExist(err) { t.Error("a DB was created — the first wake should boot fresh") } // The env file IS written — the future wake's spawn layers it (admin token). if _, err := os.Stat(filepath.Join(instanceDataDir, "hib.11ch.at.env")); err != nil { t.Errorf("env file: %v", err) } } // The flag defaults OFF: without it, provisioner creates stay systemd-managed — // the flip is a config decision, never a side effect of deploying this code. func TestControldHibernateCreatesDefaultOff(t *testing.T) { reg := controlTestEnv(t) srv, token := newTestControld(t, reg, 40) if resp := controlCreate(t, srv, token, "plain.11ch.at"); resp.StatusCode != http.StatusCreated { t.Fatalf("status = %d, want 201", resp.StatusCode) } rt, _ := reg.get("plain.11ch.at") if rt == nil || rt.Hibernate { t.Fatalf("route = %+v; the default create must stay systemd-managed", rt) } } // A hibernating space that has never woken has no local DB — its stats are // definitional (nothing to count, nothing set up, no content), answered from // the registry row alone. The tripwire proves no socket was dialed and the // recorder proves no unit was touched: a reaper sweep over dormant spaces must // never wake them. func TestControldStatsNeverWokenHibernating(t *testing.T) { reg := controlTestEnv(t) var calls []string systemctl = func(args ...string) error { calls = append(calls, strings.Join(args, " ")) return nil } const host = "dormant.11ch.at" sock := filepath.Join(instanceSocketDir, host+".sock") if err := reg.upsert(Route{Host: host, Kind: "instance", Socket: sock, Status: "ready", Hibernate: true}); err != nil { t.Fatal(err) } dials := socketTripwire(t, host) srv, token := newTestControld(t, reg, 40) req, _ := http.NewRequest("GET", srv.URL+"/instances/"+host+"/stats", nil) req.Header.Set("Authorization", "Bearer "+token) resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("stats = HTTP %d, want 200 (definitional, not unknown_host)", resp.StatusCode) } var body struct { Members int `json:"members"` SetupComplete bool `json:"setup_complete"` HasContent bool `json:"has_content"` } if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { t.Fatal(err) } if body.Members != 0 || body.SetupComplete || body.HasContent { t.Errorf("stats = %+v, want the definitional 0/false/false", body) } if n := dials.Load(); n != 0 { t.Errorf("stats dialed the instance socket %d time(s) — it must stay a pure metadata read", n) } if len(calls) != 0 { t.Errorf("stats touched systemd: %v", calls) } } // An asleep hibernating space (woken before, now stopped) answers from its // local DB — the warm cache the activator left at the last sleep — read-only, // again with zero socket touches. func TestControldStatsAsleepHibernatingAnswersFromFile(t *testing.T) { reg := controlTestEnv(t) const host = "napping.11ch.at" // The warm-cache DB: the pre-seeded admin, never set up, no content. db, err := openDB(filepath.Join(instanceDataDir, host+".db")) if err != nil { t.Fatal(err) } if _, err := db.sql.Exec(`INSERT INTO users(id, created_at) VALUES('admin',1)`); err != nil { t.Fatal(err) } db.sql.Close() sock := filepath.Join(instanceSocketDir, host+".sock") if err := reg.upsert(Route{Host: host, Kind: "instance", Socket: sock, Status: "ready", Hibernate: true}); err != nil { t.Fatal(err) } dials := socketTripwire(t, host) srv, token := newTestControld(t, reg, 40) req, _ := http.NewRequest("GET", srv.URL+"/instances/"+host+"/stats", nil) req.Header.Set("Authorization", "Bearer "+token) resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("stats = HTTP %d, want 200", resp.StatusCode) } var body struct { Members int `json:"members"` SetupComplete bool `json:"setup_complete"` HasContent bool `json:"has_content"` } if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { t.Fatal(err) } if body.Members != 1 || body.SetupComplete || body.HasContent { t.Errorf("stats = %+v, want 1/false/false (from the warm-cache DB)", body) } if n := dials.Load(); n != 0 { t.Errorf("stats dialed the instance socket %d time(s) — the DB file is the source", n) } } // The definitional answer is ONLY for hibernating rows: a host with no route at // all, and a registered systemd-managed instance whose DB is missing (a failed // create), both stay 404 — the reaper keeps skipping those, as before. func TestControldStatsMissingStays404(t *testing.T) { reg := controlTestEnv(t) srv, token := newTestControld(t, reg, 40) stat := func(host string) int { req, _ := http.NewRequest("GET", srv.URL+"/instances/"+host+"/stats", nil) req.Header.Set("Authorization", "Bearer "+token) resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } resp.Body.Close() return resp.StatusCode } if code := stat("never-was.11ch.at"); code != http.StatusNotFound { t.Errorf("unregistered host: %d, want 404", code) } if err := reg.upsert(Route{Host: "broken.11ch.at", Kind: "instance", Socket: "/x.sock", Status: "failed"}); err != nil { t.Fatal(err) } if code := stat("broken.11ch.at"); code != http.StatusNotFound { t.Errorf("non-hibernating row without a DB: %d, want 404", code) } } // Under hibernate-creates the capacity cap counts MATERIALIZED (systemd-managed) // instances only — hibernating rows cost no process, so they don't consume the // box's RAM budget; a separate, much higher ceiling bounds total registrations. func TestControldCapCountsMaterializedOnlyWhenHibernateCreates(t *testing.T) { reg := controlTestEnv(t) // Three hibernating rows against maxInstances=1: with the flag on they // don't count, so a (hibernating) create still succeeds… for _, h := range []string{"h1.11ch.at", "h2.11ch.at", "h3.11ch.at"} { if err := reg.upsert(Route{Host: h, Kind: "instance", Socket: "/run/" + h + ".sock", Status: "ready", Hibernate: true}); err != nil { t.Fatal(err) } } token := "test-control-token" c := &controld{reg: reg, tokenHash: hashToken(token), maxInstances: 1, maxRegistered: 10, minFreeGB: 5, hibernateCreates: true} srv := httptest.NewServer(c.handler()) t.Cleanup(srv.Close) if resp := controlCreate(t, srv, token, "new1.11ch.at"); resp.StatusCode != http.StatusCreated { t.Fatalf("create with 3 hibernating rows: %d, want 201 (they must not count)", resp.StatusCode) } // …a materialized row DOES hold the maxInstances cap… if err := reg.upsert(Route{Host: "always-on.11ch.at", Kind: "instance", Socket: "/run/a.sock", Status: "ready"}); err != nil { t.Fatal(err) } resp := controlCreate(t, srv, token, "new2.11ch.at") if resp.StatusCode != http.StatusServiceUnavailable { t.Fatalf("create at the materialized cap: %d, want 503", resp.StatusCode) } if m := decodeBody(t, resp); m["error"] != "at_capacity" { t.Errorf("error = %v, want at_capacity", m["error"]) } // …and the registration ceiling bounds total rows, hibernating included // (5 instance rows now: h1-h3 + new1 hibernating, always-on materialized). c2 := &controld{reg: reg, tokenHash: hashToken(token), maxInstances: 40, maxRegistered: 5, minFreeGB: 5, hibernateCreates: true} srv2 := httptest.NewServer(c2.handler()) t.Cleanup(srv2.Close) resp2 := controlCreate(t, srv2, token, "new3.11ch.at") if resp2.StatusCode != http.StatusServiceUnavailable { t.Fatalf("create at the registration ceiling: %d, want 503", resp2.StatusCode) } if m := decodeBody(t, resp2); m["error"] != "at_capacity" { t.Errorf("error = %v, want at_capacity", m["error"]) } } // --- materialize (the controld-mediated fix for mesh-replicated routes' first wake) --- func controlMaterialize(t *testing.T, srv *httptest.Server, token, host string) *http.Response { t.Helper() req, _ := http.NewRequest("POST", srv.URL+"/instances/"+host+"/materialize", nil) if token != "" { req.Header.Set("Authorization", "Bearer "+token) } resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } t.Cleanup(func() { resp.Body.Close() }) return resp } func TestControldMaterializeRequiresToken(t *testing.T) { reg := controlTestEnv(t) srv, _ := newTestControld(t, reg, 40) if resp := controlMaterialize(t, srv, "", "a.example.com"); resp.StatusCode != http.StatusUnauthorized { t.Errorf("no token: status = %d, want 401", resp.StatusCode) } } func TestControldMaterializeUnknownHost(t *testing.T) { reg := controlTestEnv(t) srv, token := newTestControld(t, reg, 40) resp := controlMaterialize(t, srv, token, "never-registered.example.com") if resp.StatusCode != http.StatusNotFound { t.Fatalf("status = %d, want 404", resp.StatusCode) } if m := decodeBody(t, resp); m["error"] != "unknown_host" { t.Errorf("error = %v, want unknown_host", m["error"]) } } // Mirrors the exact scenario found live 2026-07-13: a route with a registry // row but no live symlink (meshreplicate.go's meshRouteHandler only ever // upserts the row) must come out materialized — staged binary + live symlink // — after one call, ready for s3Backend.start to exec. func TestControldMaterializeStagesAndPointsLive(t *testing.T) { reg := controlTestEnv(t) if err := reg.upsert(Route{Host: "seeded.11ch.at", Kind: "instance", Socket: "/run/seeded.sock", Hibernate: true}); err != nil { t.Fatal(err) } if _, err := os.Stat(liveLink("seeded.11ch.at")); err == nil { t.Fatal("test setup: live symlink should not exist yet") } srv, token := newTestControld(t, reg, 40) resp := controlMaterialize(t, srv, token, "seeded.11ch.at") if resp.StatusCode != http.StatusOK { t.Fatalf("status = %d, want 200", resp.StatusCode) } m := decodeBody(t, resp) if m["ok"] != true || m["version"] == "" { t.Fatalf("body = %v, want ok:true + a version", m) } ver, err := liveVersion("seeded.11ch.at") if err != nil || ver == "" { t.Fatalf("liveVersion after materialize: %v, %v — symlink was not created", ver, err) } if _, err := os.Stat(versionBinPath(ver)); err != nil { t.Fatalf("staged binary missing at the version path: %v", err) } } // A second call (the retry a wake would make) must not fail just because the // symlink already exists — pointLive happily repoints at the same version. func TestControldMaterializeIdempotent(t *testing.T) { reg := controlTestEnv(t) if err := reg.upsert(Route{Host: "seeded2.11ch.at", Kind: "instance", Socket: "/run/seeded2.sock", Hibernate: true}); err != nil { t.Fatal(err) } srv, token := newTestControld(t, reg, 40) for i := 0; i < 2; i++ { if resp := controlMaterialize(t, srv, token, "seeded2.11ch.at"); resp.StatusCode != http.StatusOK { t.Fatalf("call %d: status = %d, want 200", i, resp.StatusCode) } } } // A box-level default home origin fills provisioner creates that name none // (LCHAT_CONTROL_HOME_ORIGIN — non-production fleets front a different home); // an explicit request value still wins. func TestControldHomeOriginDefault(t *testing.T) { reg := controlTestEnv(t) c := &controld{reg: reg, tokenHash: hashToken("test-control-token"), maxInstances: 10, minFreeGB: 5, homeOrigin: "https://home.ie.dev.example"} srv := httptest.NewServer(c.handler()) defer srv.Close() create := func(payload map[string]string) { t.Helper() body, _ := json.Marshal(payload) req, _ := http.NewRequest("POST", srv.URL+"/instances", bytes.NewReader(body)) req.Header.Set("Authorization", "Bearer test-control-token") resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != http.StatusCreated { t.Fatalf("create %v: HTTP %d", payload, resp.StatusCode) } } envOrigin := func(host string) string { t.Helper() env, err := readEnvFile(filepath.Join(instanceDataDir, host+".env")) if err != nil { t.Fatal(err) } return env["LCHAT_HOME_ORIGIN"] } create(map[string]string{"host": "defaulted.example.com"}) if got := envOrigin("defaulted.example.com"); got != "https://home.ie.dev.example" { t.Fatalf("defaulted create's LCHAT_HOME_ORIGIN = %q", got) } create(map[string]string{"host": "explicit.example.com", "home_origin": "https://home.other.example"}) if got := envOrigin("explicit.example.com"); got != "https://home.other.example" { t.Fatalf("explicit create's LCHAT_HOME_ORIGIN = %q, want the request's own", got) } }