Skip to content

formspec serve — Production Single Server ​

formspec serve menjalankan engine pada mode production: satu proses, satu deployment, tanpa Control Plane. Ia binary yang sama dengan formspec dev — yang berbeda adalah mode dan penegakan batasnya.

Untuk pengembangan sehari-hari gunakan formspec dev; dokumen ini tentang apa yang berubah saat menjalankannya untuk pengguna nyata.

bash
formspec serve --mode=production \
  --spec ./spec \
  --dsn "postgres://formspec:secret@localhost:5432/app" \
  --jwt-secret "…" \
  --cors-origin https://app.example.com \
  --tls-cert cert.pem --tls-key key.pem \
  --metrics-addr 127.0.0.1:9102 --admin-secret "$ADMIN_SECRET"

Flag ​

FlagDefaultKeterangan
--modeproductionSatu-satunya mode yang diimplementasikan. Nilai lain → exit 2 (pesan mengarahkan ke dev).
--spec./specDirektori manifest. Dibaca sekali saat boot — tidak ada watcher.
--dsn—Wajib. Postgres. DSN sqlite: ditolak.
--addr:8080Alamat REST API + UI.
--workspace(lihat di bawah)Workspace aktif. Berpengaruh pada penegakan uses, OAuth default, dynamic subscrip­tion.
--jwt-secret—HS256 shared secret. Salah satu dari ini atau --jwt-public-key wajib.
--jwt-public-key—PEM berisi public key RSA/ECDSA → RS256/ES256.
--jwt-issuerformspecIssuer yang diharapkan pada token.
--tls-cert/--tls-key—Sepasang sertifikat + key → HTTPS (min TLS 1.2). Harus diberikan bersama.
--cors-origin—Wajib, bisa diulang. * ditolak.
--web-dir(auto-detect)Direktori SPA hasil build. Kosong → urutan resolusi di bawah.
--sidecar-endpoint—Endpoint app-process untuk impl: {type: sidecar} (unix://… atau http://…).
--invoke-timeout30sBatas waktu satu invoke sidecar.
--metrics-addr127.0.0.1:9102Listener admin (/metrics, /health, /workspaces). Kosong → dimatikan.
--admin-secret—Bearer token untuk endpoint tulis /workspaces. Kosong → endpoint itu tidak di-mount.
--log-levelinfodebug | info | warn | error.
--log-debugfalseMengaktifkan record debug (kontrol operator — wajib dicatat di audit trail operator).

Batas production yang ditegakkan ​

serve berhenti di boot dengan pesan yang menyebut todo-nya, bukan diam-diam melanjutkan dengan konfigurasi yang tidak aman:

GatePerilaku
SQLite sebagai DSNditolak — production wajib Postgres (Fase 8.1.4)
JWT tidak dikonfigurasiditolak — tidak ada dev auth / identitas sintetis (8.1.2)
CORS allow-list kosong, atau *ditolak (8.1.5)
--tls-cert tanpa --tls-keyditolak (8.1.3)
--mode selain productionexit 2

Selain itu ia mematikan seluruh jalan pintas dev: tidak ada seeding, tidak ada auto-approve, uses enforcement ketat, dan root redirect GET / → /{ws}/ tidak dipasang (segmen root milik edge: subdomain per workspace atau aturan ingress).

Aset SPA ​

serve menyajikan UI dari sumber pertama yang ada:

  1. --web-dir <dir>
  2. renderers/react-shadcn/dist — di-auto-detect dari CWD ke atas
  3. cache formspec spa install (per versi binary)
  4. SPA yang ter-embed di binary (-tags formspec_spa)

Deployment dari checkout repo karena itu tidak butuh flag apa pun. Bila tidak ada satu pun yang ditemukan, banner mencatat no SPA assets — API only (sebuah 404 senyap akan menyamarkan bedanya "flag kurang" dan "build kurang").

Penyajian aset memakai HTTP caching + kompresi yang sama dengan dev (fingerprinted → immutable setahun; sisanya no-cache + ETag; preferensi br → gzip → identity dari sidecar yang ditulis formspec spa compress).

Satu bundle per proses. serve tidak mendukung SPA yang berbeda per workspace: bundle-nya satu untuk seluruh proses, dan SPA membaca slug dari URL. Yang bisa berbeda per App adalah data — tema (App.spec.theme_ref), logo/favicon, app_renderer/chrome, dan komponen asset dari module. Untuk front-end yang benar-benar berbeda per workspace, jalankan satu deployment per workspace.

Runtime action ​

impl.typeDi serve
scriptBerjalan (Starlark sandbox).
nativeBerjalan bila handler-nya terdaftar di binary (App.RegisterNative).
sidecarBerjalan bila --sidecar-endpoint di-set; tanpa itu gagal dengan pesan jelas.

Listener ctx.* untuk app process non-Go (--listen/--app-endpoint) dan spawning app child process belum ada di serve — keduanya hanya di formspec dev. Perencanaan: docs_internal/plan/serve-parity.md.

Listener admin ​

--metrics-addr menyajikan:

EndpointIsi
GET /metricsPrometheus (todo 8.2.4, 09-observability.md §3.1)
GET /health{status, reasons, checked_at} (8.2.6); 503 saat unhealthy
GET /workspacesDaftar workspace terdaftar — hanya bila --admin-secret
POST /workspaces{"slug":"kopi","name":"Kopi Kita"} → daftarkan
DELETE /workspaces/{slug}Cabut registrasi (soft; data tenant tidak disentuh)

Default bind-nya loopback (127.0.0.1:9102) karena listener ini tidak memiliki autentikasi. /metrics dan /health dijangkau operator lewat loopback atau port-forward; mengeksposnya ke jaringan (mis. --metrics-addr :9102) harus keputusan sadar. Tanpa --admin-secret, endpoint /workspacestidak di-mount sama sekali — bukan menjawab 401, melainkan tidak ada — sehingga tidak ada permukaan tulis untuk diserang.

Menambah / menghapus workspace tanpa restart ​

Slug URL workspace divalidasi per request terhadap registry di database (formspec.core/workspace). Karena itu workspace yang baru didaftarkan langsung routable pada request berikutnya — tidak ada proses restart, tidak ada cache yang perlu dibuang:

bash
curl -H "Authorization: Bearer $ADMIN_SECRET" \
     -d '{"slug":"cabang2","name":"Cabang 2"}' \
     http://127.0.0.1:9102/workspaces
# → 201 {"slug":"cabang2","created":true}

curl http://127.0.0.1:8080/cabang2/_ui/_meta/apps    # sudah 200, tanpa restart

Admin pertama untuk workspace baru dibuat lewat wizard setup pemilik workspace itu: POST /{ws}/_ui/setup (hanya berlaku selama workspace belum punya user).

Aturan yang perlu diketahui:

  • Slug divalidasi dengan aturan loader manifest yang sama: kebab-case, bukan segmen reserved (_ui, api, _admin, assets, health, login, register, _ws, print).
  • Mendaftarkan slug yang sudah ada bersifat idempoten (created: false, display name diperbarui) — sumber deklaratif (kind: Workspace) dan jalur runtime bertemu di baris yang sama.
  • DELETE hanya mencabut registrasi; baris data tenant tetap dan bisa dibaca lagi bila slug didaftarkan ulang. Menghapus data tenant adalah operasi tersendiri (formspec backup), bukan efek samping perubahan routing.
  • Workspace default tidak bisa dihapus: ia fallback CLI/SPA dan di-seed ulang setiap boot.
  • Menghapus manifest kind: Workspace tidak mencabut registrasi (syncWorkspaceRegistry hanya meng-Ensure, tidak pernah menghapus).

Batas yang disadari ​

BatasAlasan / jalur keluar
Satu DSN per prosesSemua workspace berbagi satu database, dipisah tenant_id. Isolasi fisik = satu deployment per workspace. kind: Datastore + access.filter.workspaces ada, tetapi binding snapshot (level 3) belum ter-wire di single-server.
Tidak ada hot-reload specManifest dibaca saat boot. Perubahan manifest = restart.
Kredensial datastore lewat envcredential_ref (kms://…) dideklarasikan di spec tetapi belum ada resolver KMS/Vault.
Storage fallback = filesystemTanpa kind: Datastore object store, ctx.storage menulis ke <project-root>/.formspec/storage. Di container itu ephemeral — pakai object store atau volume persisten.
Migrasi destruktif menolak bootGerbang sengaja. Jalankan formspec migrate apply lebih dulu (atau formspec repl --no-sync -f repair.star untuk repair).
SPA berbeda per workspaceSatu bundle per proses (lihat "Aset SPA").
App process non-GoBelum ada listener ctx.* / spawning child process di serve.

Sisa pekerjaan terbuka: docs_internal/plan/serve-parity.md dan item [⏸️] di Fase 8 docs_internal/plan/todo.md.

Standar terbuka (CC0) dengan implementasi referensi.