feat(apps): add modular hosting backends and AWS Lightsail #4658
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| # Pull-request gate. Required to pass before merging into protected branches | |
| # (SOC 2 CC8.1 — automated checks run on every change before review/merge). | |
| on: | |
| pull_request: | |
| branches: [main, staging, prod] | |
| workflow_dispatch: | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| # Detect which parts of the repo a PR touches so each build job runs only when | |
| # something in its dependency closure changed — no point typechecking the API | |
| # for a frontend-only PR, or rebuilding the desktop app for an API-only one. | |
| # | |
| # Each filter = the app's own dir + every workspace package it depends on (its | |
| # pnpm `--filter "X..."` closure) + the shared foundation files that can break | |
| # any pnpm build. The Bun-based sandbox agent has its own in-dir lockfile, so it | |
| # only shares tsconfig.base.json; desktop runs no TS typecheck. | |
| # | |
| # A job gated `if: needs.changes.outputs.* == 'true'` reports as "skipped" when | |
| # it doesn't run, and GitHub treats a skipped job as passing — safe even if | |
| # these are later made required checks. workflow_dispatch always runs the full | |
| # suite (the `|| workflow_dispatch` guard on each job). | |
| changes: | |
| name: Detect changes | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| outputs: | |
| api: ${{ steps.filter.outputs.api }} | |
| frontend: ${{ steps.filter.outputs.frontend }} | |
| cli: ${{ steps.filter.outputs.cli }} | |
| sandbox_agent: ${{ steps.filter.outputs.sandbox_agent }} | |
| desktop: ${{ steps.filter.outputs.desktop }} | |
| self_host: ${{ steps.filter.outputs.self_host }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: dorny/paths-filter@v4 | |
| id: filter | |
| with: | |
| filters: | | |
| # Shared foundation for the pnpm-installed jobs (api/frontend/cli): | |
| # a change here can break any of their installs/typechecks. | |
| pnpm: &pnpm | |
| - 'pnpm-lock.yaml' | |
| - 'pnpm-workspace.yaml' | |
| - 'package.json' | |
| - '.npmrc' | |
| - 'tsconfig.base.json' | |
| - '.github/workflows/ci.yml' | |
| api: | |
| - *pnpm | |
| - 'apps/api/**' | |
| - 'apps/kortix-app-runtime/**' | |
| - 'packages/agent-tunnel/**' | |
| - 'packages/api-contract/**' | |
| - 'packages/db/**' | |
| - 'packages/manifest-schema/**' | |
| - 'packages/shared/**' | |
| - 'packages/starter/**' | |
| frontend: | |
| - *pnpm | |
| - 'apps/web/**' | |
| - 'packages/shared/**' | |
| cli: | |
| - *pnpm | |
| - 'apps/cli/**' | |
| - 'packages/manifest-schema/**' | |
| - 'packages/starter/**' | |
| sandbox_agent: | |
| - '.github/workflows/ci.yml' | |
| - 'tsconfig.base.json' | |
| - 'apps/kortix-sandbox-agent-server/**' | |
| desktop: | |
| - '.github/workflows/ci.yml' | |
| - 'apps/desktop-electron/**' | |
| # self-host stack: anything that can break `kortix self-host start`'s | |
| # from-scratch DB bootstrap or the CLI's compose generation. | |
| self_host: | |
| - *pnpm | |
| - 'apps/api/Dockerfile' | |
| - 'apps/kortix-app-runtime/**' | |
| - 'apps/api/src/snapshots/builder.ts' | |
| - 'apps/api/src/config.ts' | |
| - 'packages/db/**' | |
| - 'apps/cli/src/commands/self-host.ts' | |
| - 'apps/cli/src/self-host/**' | |
| - 'apps/cli/scripts/self-host-e2e/**' | |
| api-typecheck: | |
| name: API typecheck | |
| needs: changes | |
| if: needs.changes.outputs.api == 'true' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Install Node | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 22 | |
| - name: Install pnpm | |
| # The lockfile is pnpm 8 format (lockfileVersion 6.0), matching the | |
| # packageManager pin. corepack provides exactly that version. | |
| run: | | |
| corepack enable pnpm | |
| echo "pnpm: $(pnpm -v) node: $(node -v)" | |
| - name: Install dependencies (kortix-api + workspace deps) | |
| # engine-strict is relaxed for CI only: an out-of-scope web dep declares | |
| # engines pnpm>=10/node>=24, which conflicts with the repo's pnpm 8 | |
| # lockfile. This relaxes the version check only — supply-chain controls | |
| # (minimum-release-age, ignore-scripts) stay enforced. | |
| run: pnpm install --frozen-lockfile --filter "kortix-api..." | |
| env: | |
| npm_config_engine_strict: "false" | |
| - name: Typecheck | |
| run: pnpm --filter kortix-api typecheck | |
| frontend-build: | |
| name: Frontend build | |
| needs: changes | |
| if: needs.changes.outputs.frontend == 'true' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Install Node | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 22 | |
| - name: Install Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install pnpm | |
| run: | | |
| corepack enable pnpm | |
| echo "pnpm: $(pnpm -v) node: $(node -v)" | |
| - name: Install dependencies (frontend + workspace deps) | |
| run: pnpm install --frozen-lockfile --filter "./apps/web..." | |
| env: | |
| npm_config_engine_strict: "false" | |
| - name: Enforce frontend SDK boundary | |
| run: | | |
| pnpm --dir apps/web exec bun test src/sdk-boundary.test.ts | |
| pnpm --dir apps/web exec eslint src --quiet | |
| # Next.js 16.3 turns on Turbopack's FileSystem cache for `next build` | |
| # (`experimental.turbopackFileSystemCacheForBuild`, default true) and | |
| # writes it to apps/web/.next/cache/turbopack. A bare runner starts with | |
| # no .next, so without this step 16.3 pays the cost of WRITING that cache | |
| # on every run and never reads it back. Restoring it is what turns the | |
| # 16.3 build-cache feature into an actual CI speedup. | |
| # | |
| # Key on the lockfile plus this run's SHA so every run saves a fresh | |
| # entry (actions/cache never overwrites an existing key); the restore-key | |
| # falls back to the newest entry built against the same dependency set. | |
| # Turbopack invalidates per-file from its own content hashes, so a | |
| # restored cache from an older SHA is a valid starting point, not a | |
| # source of stale output. | |
| # | |
| # The fallback deliberately stops at the lockfile hash. A broader | |
| # `nextjs-turbopack-<os>-` key would also match caches built against a | |
| # DIFFERENT next version, whose on-disk cache format need not be | |
| # compatible — that is ~1GB downloaded to be discarded. Upstream's recipe | |
| # scopes its restore-keys to the lockfile for the same reason: | |
| # https://nextjs.org/docs/app/guides/ci-build-caching | |
| # | |
| # Verified on this PR: run 1 logged "Cache not found" then "Cache saved"; | |
| # a re-run logged "Cache restored from key" and the Turbopack compile | |
| # went 105s -> 1.549s. | |
| - name: Restore Turbopack build cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: apps/web/.next/cache | |
| key: nextjs-turbopack-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ github.sha }} | |
| restore-keys: | | |
| nextjs-turbopack-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}- | |
| - name: Build standalone frontend | |
| run: pnpm --filter ./apps/web build | |
| env: | |
| NODE_OPTIONS: --max-old-space-size=6144 | |
| NEXT_PUBLIC_BACKEND_URL: http://localhost:8008/v1 | |
| NEXT_PUBLIC_SUPABASE_URL: https://placeholder.supabase.co | |
| NEXT_PUBLIC_SUPABASE_ANON_KEY: local-build-placeholder-anon-key | |
| NEXT_PUBLIC_BILLING_ENABLED: "false" | |
| NEXT_OUTPUT: standalone | |
| sandbox-agent-build: | |
| name: Sandbox agent build | |
| needs: changes | |
| if: needs.changes.outputs.sandbox_agent == 'true' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Install Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| working-directory: apps/kortix-sandbox-agent-server | |
| - name: Typecheck | |
| run: bun run typecheck | |
| working-directory: apps/kortix-sandbox-agent-server | |
| - name: Build Linux sandbox agent daemon | |
| run: BUN_COMPILE_TARGET=bun-linux-x64 bun run build | |
| working-directory: apps/kortix-sandbox-agent-server | |
| - name: Verify binary exists | |
| run: | | |
| test -x apps/kortix-sandbox-agent-server/dist/kortix-agent | |
| ls -lh apps/kortix-sandbox-agent-server/dist/kortix-agent | |
| cli-binary-smoke: | |
| name: CLI binary smoke | |
| needs: changes | |
| if: needs.changes.outputs.cli == 'true' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Install Node | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 22 | |
| - name: Install Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install pnpm | |
| run: | | |
| corepack enable pnpm | |
| echo "pnpm: $(pnpm -v) node: $(node -v) bun: $(bun -v)" | |
| - name: Install dependencies (CLI + workspace deps) | |
| run: pnpm install --frozen-lockfile --filter "@kortix/cli..." | |
| env: | |
| npm_config_engine_strict: "false" | |
| - name: Build Linux CLI binary | |
| run: | | |
| bun build \ | |
| --compile \ | |
| --minify \ | |
| --target=bun-linux-x64 \ | |
| --outfile=/tmp/kortix-cli-smoke \ | |
| apps/cli/src/index.ts | |
| - name: Smoke test binary | |
| run: /tmp/kortix-cli-smoke version | |
| # Always-on gate against the self-host schema-bootstrap regression class: a | |
| # fresh `kortix self-host` database must come up fully provisioned. Boots the | |
| # data plane (Postgres + Supabase + the kortix-migrate one-shot + the API) and | |
| # asserts the migrate one-shot applies all migrations and the | |
| # owner/account flow works. This is a deployment-topology gate. Product | |
| # behavior remains covered by the root test command. | |
| self-host-schema: | |
| name: Self-host schema bootstrap | |
| needs: changes | |
| if: needs.changes.outputs.self_host == 'true' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Install Node | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 22 | |
| - name: Install Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install pnpm | |
| run: | | |
| corepack enable pnpm | |
| echo "pnpm: $(pnpm -v) node: $(node -v) bun: $(bun -v)" | |
| - name: Install dependencies (CLI + workspace deps) | |
| run: pnpm install --frozen-lockfile --filter "@kortix/cli..." | |
| env: | |
| npm_config_engine_strict: "false" | |
| - name: Build API image | |
| run: docker build --build-arg SERVICE=apps/api -f apps/api/Dockerfile -t kortix/kortix-api:selfhost-local . | |
| - name: Self-host schema-bootstrap check (fresh DB) | |
| run: bash apps/cli/scripts/self-host-e2e/schema-check.sh | |
| env: | |
| API_IMAGE: kortix/kortix-api:selfhost-local | |
| desktop-installer-smoke: | |
| name: Desktop installer smoke | |
| # Skip on PRs that don't touch desktop code. Push-to-main builds (desktop.yml) | |
| # still cover the full signed release artifacts. | |
| needs: changes | |
| if: needs.changes.outputs.desktop == 'true' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-22.04 | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Install Node | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 22 | |
| - name: Install pnpm | |
| run: corepack enable pnpm | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile --filter @kortix/desktop-electron | |
| env: | |
| npm_config_engine_strict: "false" | |
| - name: Ensure Electron runtime | |
| run: pnpm --filter @kortix/desktop-electron exec node scripts/ensure-runtime.js | |
| - name: Build Linux desktop installer (Electron, unsigned) | |
| run: pnpm --filter @kortix/desktop-electron exec electron-builder --linux --publish never | |
| env: | |
| KORTIX_DESKTOP_DEFAULT_URL: https://dev.kortix.com/projects | |
| CSC_IDENTITY_AUTO_DISCOVERY: "false" | |
| - name: Verify installer exists | |
| run: | | |
| shopt -s nullglob | |
| files=(apps/desktop-electron/dist/*.AppImage) | |
| test "${#files[@]}" -gt 0 | |
| ls -lh "${files[@]}" | |
| dependency-scan: | |
| name: Dependency + secret scan | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Trivy filesystem (fail on CRITICAL) | |
| uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 | |
| with: | |
| scan-type: fs | |
| scan-ref: . | |
| scanners: vuln,secret | |
| severity: CRITICAL | |
| ignore-unfixed: true | |
| exit-code: "1" | |
| format: table |