Skip to content

Latest commit

 

History

History
185 lines (148 loc) · 7.97 KB

File metadata and controls

185 lines (148 loc) · 7.97 KB

Phase 0 — Planning & Architecture

Status: design frozen for Phase 1. Open questions marked .

1. Product

Self-hostable deployment platform. A single Docker Compose brings up the control plane; the same app pushes user code to a fleet of worker nodes (targets: bare metal, Hetzner, your laptop). Comparable in scope to Coolify/Dokploy, simpler than Vercel.

Two surfaces:

  • Dashboard — Inertia/React SPA served by the same Rails app, cookie session.
  • API — JSON over HTTP, token auth, used by CLI and worker agents.

2. High-level architecture

                ┌──────────────────────────────────────────────┐
                │            kaelix (control plane)            │
   Browser ───► │  Rails 8.1                                   │
   (Inertia)    │  ├─ Web (Puma) — Inertia + JSON API          │
                │  ├─ Worker (Solid Queue) — build/deploy jobs │
   CLI ───────► │  └─ Solid Queue supervisor / cron            │
   (API token)  └─────┬───────────────────────────┬──────────┘
                      │                           │
                      ▼                           ▼
                ┌──────────┐               ┌──────────────┐
                │ Postgres │               │ Target nodes │
                │  (one)   │               │ (ssh agents) │
                └──────────┘               └──────────────┘

One Postgres, three logical schemas (one DB, separate schema_search_path):

  • public — app tables (users, orgs, projects, deployments, …).
  • solid_queue_* — Solid Queue tables (managed by gem migrations).
  • solid_cache_* — Solid Cache tables.
  • solid_cable_* — Solid Cable tables.

No Redis in Phase 1. All state is in Postgres. If/when we need Redis (ActionCable at scale, rate limiting, ephemeral pubsub), add it then.

3. Tech choices

Concern Choice Why
App framework Rails 8.1.3 Already set up.
Frontend Inertia.js + React 19 + Tailwind v4 Already set up.
JS toolchain Bun (package manager + Vite runtime) Already set up.
Database PostgreSQL 16 JSONB, partial indexes, FTS.
Background jobs Solid Queue Postgres-only. No Redis.
Cache Solid Cache Postgres-only.
WebSockets Solid Cable Postgres-only.
Auth (web) Devise (session cookies) Battle-tested.
Auth (API) ApiKey (Bearer token) Opaque, scoped, revocable.
Authorization Pundit De-facto Rails RBAC.
Object storage S3-compatible (Active Storage) Deferred — Phase 2.
Object map pg Native.

4. Domain model (Phase 1 scope highlighted)

User
  id, email, encrypted_password, …
  has_many :memberships
  has_many :organizations, through: :memberships
  has_many :api_keys

Organization              ◀── tenant boundary
  id, name, slug, owner_id
  has_many :memberships
  has_many :users, through: :memberships
  has_many :projects
  has_many :api_keys

Membership                ◀── RBAC junction
  id, user_id, organization_id, role
  role ∈ {owner, admin, developer, viewer}
  unique [user_id, organization_id]

ApiKey
  id, organization_id, name, token_digest, scopes[], last_used_at, expires_at
  scope ∈ {read, write, deploy, admin}

Project                   ─ Phase 1 CRUD; deployments/servers in Phase 2
  id, organization_id, name, slug, repository_url, branch,
  framework, build_command, output_directory, status

Deferred to later phases (tables NOT created yet, but reserved slugs):

Server         — registered target nodes (hetzner, aws, local)
Deployment     — one row per push, links project → server → logs
Domain         — custom domains + TLS provisioning
EnvVar         — per-project secrets (encrypted)
Template       — "Next.js + Postgres" one-click recipes
Volume         — persistent storage requests on a server
Log            — append-only, partitioned by deployment
Webhook        — github/gitlab push events
Notification   — slack/email/webhook

5. RBAC matrix

memberships.role (in app/policies/*.rb):

Action owner admin developer viewer
Manage org settings
Invite/remove members
Create/rotate API keys
Create/edit projects
Trigger deployment
View projects/deploys
Delete org

API keys have an independent scope set on the key itself, additive to membership. e.g. a deploy-scoped token can deploy but not edit projects.

6. API contracts (v1, all under /api/v1)

POST   /auth/login                 { email, password }         → { token, user }
POST   /auth/logout                                              → 204
GET    /me                                                       → { user, current_org }

GET    /organizations
POST   /organizations               { name }
GET    /organizations/:id
PATCH  /organizations/:id           { name }
DELETE /organizations/:id

GET    /organizations/:id/memberships
POST   /organizations/:id/memberships   { email, role }
PATCH  /organizations/:id/memberships/:mid   { role }
DELETE /organizations/:id/memberships/:mid

GET    /organizations/:id/projects
POST   /organizations/:id/projects   { name, repository_url, … }
GET    /projects/:id
PATCH  /projects/:id
DELETE /projects/:id

GET    /organizations/:id/api_keys
POST   /organizations/:id/api_keys   { name, scopes[] }        → { token: "…shown once…" }
DELETE /organizations/:id/api_keys/:id

Auth: Authorization: Bearer <token>. Errors: { error: { code, message } }.

7. Deployment workflow (Phase 2 design, recorded for context)

git push
  └─► webhook (Phase 2)
        └─► Deployments::CreateJob
              ├─ clone repo
              ├─ detect framework
              ├─ upload build context to target server
              ├─ run build (on target or build node)
              ├─ stream logs → Log rows + ActionCable
              └─ finalize: swap traffic, set DNS

Phase 1 only ships the entities + RBAC + API plumbing. The job above is a stub Deployments::CreateJob that logs "would deploy" so the pipeline is exercised end-to-end.

8. Local dev quickstart (target)

docker compose up
# postgres, web, worker, vite come up
# web:  http://localhost:3000   (dashboard + API)
# vite: http://localhost:3036   (HMR, proxied by web)
# queue: Solid Queue dashboard at /admin/queue (Phase 2)

9. Open questions

  • Multi-region / HA for the control plane? Out of scope for Phase 1.
  • Build on control plane vs on target? Default to target, allow override per project.
  • Pricing / metering? Not in this repo — keep Plan out of the schema.