| index d6ab09c33..6c572811a 100644 |
| --- a/internal/registry/registry.go |
| +++ b/internal/registry/registry.go |
| @@ -50,9 +50,20 @@ type Route struct { |
| Replication string // "" | "stream" | "batched" | "daily" |
| NextDue int64 |
| |
| + // Backing is who owns the instance's process: "" = an exec child of the |
| + // agent (the activator spawns live/<host> itself — the Eleven shape), or |
| + // BackingUnit = a carlos-app@<host> systemd unit (whole-app tenants: |
| + // DynamicUser, StateDirectory) that the activator drives via systemctl. |
| + Backing string |
| + |
| CreatedAt int64 |
| } |
| |
| +// BackingUnit marks a route whose process is a carlos-app@<host> systemd unit |
| +// rather than an exec child of the agent. The empty string stays the exec |
| +// default so pre-existing rows keep today's behavior byte-identical. |
| +const BackingUnit = "unit" |
| + |
| func Open(path string) (*Registry, error) { |
| d, err := sql.Open("sqlite", path) |
| if err != nil { |
| @@ -111,6 +122,7 @@ CREATE TABLE IF NOT EXISTS routes ( |
| `ALTER TABLE routes ADD COLUMN canary INTEGER NOT NULL DEFAULT 0`, |
| `ALTER TABLE routes ADD COLUMN replication TEXT NOT NULL DEFAULT ''`, |
| `ALTER TABLE routes ADD COLUMN next_due INTEGER NOT NULL DEFAULT 0`, |
| + `ALTER TABLE routes ADD COLUMN backing TEXT NOT NULL DEFAULT ''`, |
| } { |
| if _, err := r.sql.Exec(stmt); err != nil && !strings.Contains(err.Error(), "duplicate column") { |
| return err |
| @@ -121,14 +133,15 @@ CREATE TABLE IF NOT EXISTS routes ( |
| |
| const routeCols = `host, slug, kind, socket, addr, status, last_error, db_path, |
| version, prev_version, app, pool, region, channel, hibernate, canary, |
| -replication, next_due, created_at` |
| +replication, next_due, backing, created_at` |
| |
| func scanRoute(scan func(...any) error) (Route, error) { |
| var t Route |
| var hib, can int |
| err := scan(&t.Host, &t.Slug, &t.Kind, &t.Socket, &t.Addr, &t.Status, |
| &t.LastError, &t.DBPath, &t.Version, &t.PrevVersion, &t.App, &t.Pool, |
| - &t.Region, &t.Channel, &hib, &can, &t.Replication, &t.NextDue, &t.CreatedAt) |
| + &t.Region, &t.Channel, &hib, &can, &t.Replication, &t.NextDue, &t.Backing, |
| + &t.CreatedAt) |
| t.Hibernate = hib != 0 |
| t.Canary = can != 0 |
| return t, err |
| @@ -205,7 +218,7 @@ func (r *Registry) Upsert(t Route) error { |
| } |
| _, err := r.sql.Exec(` |
| INSERT INTO routes (`+routeCols+`) |
| -VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |
| +VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |
| ON CONFLICT(host) DO UPDATE SET |
| slug=excluded.slug, kind=excluded.kind, socket=excluded.socket, |
| addr=excluded.addr, status=excluded.status, last_error=excluded.last_error, |
| @@ -213,10 +226,12 @@ ON CONFLICT(host) DO UPDATE SET |
| prev_version=excluded.prev_version, app=excluded.app, pool=excluded.pool, |
| region=excluded.region, channel=excluded.channel, |
| hibernate=excluded.hibernate, canary=excluded.canary, |
| - replication=excluded.replication, next_due=excluded.next_due`, |
| + replication=excluded.replication, next_due=excluded.next_due, |
| + backing=excluded.backing`, |
| t.Host, t.Slug, t.Kind, t.Socket, t.Addr, t.Status, t.LastError, |
| t.DBPath, t.Version, t.PrevVersion, t.App, t.Pool, t.Region, t.Channel, |
| - b2i(t.Hibernate), b2i(t.Canary), t.Replication, t.NextDue, t.CreatedAt) |
| + b2i(t.Hibernate), b2i(t.Canary), t.Replication, t.NextDue, t.Backing, |
| + t.CreatedAt) |
| return err |
| } |
| |
| @@ -282,7 +297,8 @@ func (r *Registry) Fingerprint() (string, error) { |
| err = db.QueryRow(` |
| SELECT COUNT(*) || ':' || COALESCE(MAX(rowid),0) || ':' || |
| COALESCE(SUM(rowid*17 + LENGTH(status)*3 + LENGTH(socket) + LENGTH(addr) |
| - + LENGTH(version)*5 + hibernate*7 + LENGTH(channel)),0) |
| + + LENGTH(version)*5 + hibernate*7 + LENGTH(channel) |
| + + LENGTH(backing)*11),0) |
| FROM routes`).Scan(&fp) |
| return fp, err |
| } |