feat: v2.0 Phase 1 foundation + bastion-restart identity fix + Dockerfile + BASTION_DIR #14

Merged
michal merged 5 commits from feat/v2-phase1-foundation into main 2026-05-05 21:10:26 +00:00
Owner

Summary

Lands the v2.0 Phase 1 foundation along with three smaller fixes that complete the in-flight work on this branch.

v2.0 Phase 1 foundation (commit 04faa07)

New @lab/core package: Resource types, Output<T> (Pulumi), audit/auth/environment/account types, resource kind registry.

New Prisma schema (mcpctl pattern): User/Session/Group/GroupMember, ServiceAccount, RbacDefinition, AuditEvent (with correlation IDs), Environment/Account/Binding, generic Resource model, Secret/Fleet/FleetMember/GitSource. Keeps v1.0 models (Server/Agent/Bastion/Cluster/JoinToken).

New labd services: AuthService (bearer login, bootstrap, 30-day sessions), RbacService (env-scoped permission checks, group membership, role hierarchy), AuditService (fire-and-forget batch 50 / flush 5s with correlation IDs), ResourceStore (CRUD with origin/managedBy + RBAC).

New labd routes: /api/auth/login, /api/auth/logout, RBAC-enforced /api/resources CRUD, /api/environments, /api/accounts, /api/accounts/bind, /api/bindings, /api/events. Bearer auth middleware.

fix(labd): preserve machine identity across bastion restarts (d6e1f3c)

The worker0-k8s0 bug. When labd restarted, the next DHCP/PXE re-discovery overwrote status="discovered" on top of an already-installed DB record, erasing hostname/role identity from the CLI view. Two changes plus three vitest cases.

build(labd): include @lab/core in the Dockerfile build chain (37a3b51)

The v2.0 Phase 1 commit added @lab/core but the Dockerfile only copied @lab/shared and @lab/labd. Both build stages updated; build order is sharedcorelabd to match TS project references.

feat(cli): honor BASTION_DIR env var (98b0ccc)

bastion serve/stop --dir defaults to process.env.BASTION_DIR ?? "/tmp/lab-bastion" so a deployed daemon can run from a persistent directory without callers passing --dir every time.

Test plan

  • vitest --run src/labd — 52 pass (incl. 3 new bastions-machines cases)
  • pnpm --filter {@lab/shared,@lab/core,@lab/labd} build — all clean in Dockerfile order
  • Docker image build of labd in CI / local
  • First bootstrap: POST /api/auth/login with no users seeds the admin
  • RBAC: env-scoped role binding actually denies
  • AuditService correlation-id chains visible in /api/events
## Summary Lands the v2.0 Phase 1 foundation along with three smaller fixes that complete the in-flight work on this branch. ### v2.0 Phase 1 foundation (commit 04faa07) New `@lab/core` package: Resource types, `Output<T>` (Pulumi), audit/auth/environment/account types, resource kind registry. New Prisma schema (mcpctl pattern): User/Session/Group/GroupMember, ServiceAccount, RbacDefinition, AuditEvent (with correlation IDs), Environment/Account/Binding, generic Resource model, Secret/Fleet/FleetMember/GitSource. Keeps v1.0 models (Server/Agent/Bastion/Cluster/JoinToken). New labd services: `AuthService` (bearer login, bootstrap, 30-day sessions), `RbacService` (env-scoped permission checks, group membership, role hierarchy), `AuditService` (fire-and-forget batch 50 / flush 5s with correlation IDs), `ResourceStore` (CRUD with origin/managedBy + RBAC). New labd routes: `/api/auth/login`, `/api/auth/logout`, RBAC-enforced `/api/resources` CRUD, `/api/environments`, `/api/accounts`, `/api/accounts/bind`, `/api/bindings`, `/api/events`. Bearer auth middleware. ### fix(labd): preserve machine identity across bastion restarts (d6e1f3c) The worker0-k8s0 bug. When labd restarted, the next DHCP/PXE re-discovery overwrote `status="discovered"` on top of an already-installed DB record, erasing hostname/role identity from the CLI view. Two changes plus three vitest cases. ### build(labd): include @lab/core in the Dockerfile build chain (37a3b51) The v2.0 Phase 1 commit added `@lab/core` but the Dockerfile only copied `@lab/shared` and `@lab/labd`. Both build stages updated; build order is `shared` → `core` → `labd` to match TS project references. ### feat(cli): honor BASTION_DIR env var (98b0ccc) `bastion serve`/`stop` `--dir` defaults to `process.env.BASTION_DIR ?? "/tmp/lab-bastion"` so a deployed daemon can run from a persistent directory without callers passing `--dir` every time. ## Test plan - [x] `vitest --run src/labd` — 52 pass (incl. 3 new bastions-machines cases) - [x] `pnpm --filter {@lab/shared,@lab/core,@lab/labd} build` — all clean in Dockerfile order - [ ] Docker image build of labd in CI / local - [ ] First bootstrap: `POST /api/auth/login` with no users seeds the admin - [ ] RBAC: env-scoped role binding actually denies - [ ] AuditService correlation-id chains visible in `/api/events`
michal added 5 commits 2026-05-05 21:10:21 +00:00
New packages:
- @lab/core: Resource types, Output<T> (Pulumi), audit event types,
  auth types, environment/account types, resource kind registry

New Prisma schema (mcpctl pattern):
- User (email/password/bcrypt), Session (bearer tokens), Group, GroupMember
- ServiceAccount, RbacDefinition (JSON subjects + roleBindings)
- AuditEvent (correlation IDs, causal chains, fire-and-forget batching)
- Environment, Account (driver config, Infisical secret path), Binding
- Resource (generic, kind/name/env unique, origin/managedBy tracking)
- Secret, Fleet, FleetMember, GitSource
- Keeps v1.0 models: Server, Agent, Bastion, Cluster, JoinToken

New services:
- AuthService: bearer token login, bootstrap (first login creates admin),
  session management with 30-day expiry
- RbacService: environment-scoped permission checks, group membership,
  role hierarchy (admin > edit > view)
- AuditService: fire-and-forget event collection, batch 50 / flush 5s,
  correlation IDs for causal chains
- ResourceStore: CRUD with origin/managedBy, RBAC-enforced routes

New routes:
- POST /api/auth/login, POST /api/auth/logout (bearer token auth)
- GET/POST/PUT/DELETE /api/resources (RBAC-enforced CRUD)
- GET/POST /api/environments, GET/POST /api/accounts
- POST /api/accounts/bind, GET /api/bindings
- GET /api/events (audit query with --last, --kind, --env, --correlation)

New middleware:
- Bearer token auth (validates Authorization header, resolves user identity)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The worker0-k8s0 bug: when labd restarts, the in-memory installed map
is lost. The next DHCP/PXE re-discovery for that MAC ran an upsert that
wrote status="discovered", silently downgrading the DB record from
"online" or "offline" and erasing the machine's known hostname/role
identity from the CLI view.

- server.ts: drop status="discovered" from the upsert update branch so
  re-discovery cannot downgrade an installed record.
- routes/bastions.ts (/api/machines): when the DB knows a real
  hostname+role for a MAC currently only in live.discovered, promote
  it back to live.installed so the CLI sees the right state. Also
  reordered the live-vs-DB fallback so DB online/offline maps to
  live.installed and the discovered branch is the else.
- tests: 3 new vitest cases covering promotion, fresh-discovery, and
  unknown-MAC fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The v2.0 Phase 1 commit (04faa07) introduced the @lab/core package but
the labd Dockerfile still only copied @lab/shared and @lab/labd, so the
container build would fail to resolve @lab/core imports.

Both stages updated:
- Builder: copy @lab/core package.json/tsconfig + src, add it to the
  build order between @lab/shared and @lab/labd.
- Runtime: copy @lab/core dist and package.json into the final image.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(cli): honor BASTION_DIR env var as default for --dir
Some checks failed
CI/CD / typecheck (pull_request) Failing after 21s
CI/CD / test (pull_request) Failing after 22s
CI/CD / lint (pull_request) Failing after 7m2s
CI/CD / build (pull_request) Has been skipped
CI/CD / publish-rpm (pull_request) Has been skipped
CI/CD / publish-deb (pull_request) Has been skipped
98b0ccc6c9
bastion serve/stop default for --dir was hardcoded to /tmp/lab-bastion.
Now reads BASTION_DIR from env if set, so a deployed bastion daemon
can run from a persistent directory without callers having to pass
--dir on every invocation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
michal merged commit f3c50f71ef into main 2026-05-05 21:10:26 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: michal/lab#14