gitspace 0.2.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +21 -0
- package/.gitspace/bundle.json +50 -0
- package/.gitspace/select/01-status.sh +40 -0
- package/.gitspace/setup/01-install-deps.sh +12 -0
- package/.gitspace/setup/02-typecheck.sh +16 -0
- package/AGENTS.md +439 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +25 -0
- package/README.md +607 -0
- package/bin/gssh +62 -0
- package/bun.lock +647 -0
- package/docs/CONNECTION.md +623 -0
- package/docs/GATEWAY-WORKER.md +319 -0
- package/docs/GETTING-STARTED.md +448 -0
- package/docs/GITSPACE-PLATFORM.md +1819 -0
- package/docs/INFRASTRUCTURE.md +1347 -0
- package/docs/PROTOCOL.md +619 -0
- package/docs/QUICKSTART.md +174 -0
- package/docs/RELAY.md +327 -0
- package/docs/REMOTE-DESIGN.md +549 -0
- package/docs/ROADMAP.md +564 -0
- package/docs/SITE_DOCS_FIGMA_MAKE.md +1167 -0
- package/docs/STACK-DESIGN.md +588 -0
- package/docs/UNIFIED_ARCHITECTURE.md +292 -0
- package/experiments/pty-benchmark.ts +148 -0
- package/experiments/pty-latency.ts +100 -0
- package/experiments/router/client.ts +199 -0
- package/experiments/router/protocol.ts +74 -0
- package/experiments/router/router.ts +217 -0
- package/experiments/router/session.ts +180 -0
- package/experiments/router/test.ts +133 -0
- package/experiments/socket-bandwidth.ts +77 -0
- package/homebrew/gitspace.rb +45 -0
- package/landing-page/ATTRIBUTIONS.md +3 -0
- package/landing-page/README.md +11 -0
- package/landing-page/bun.lock +801 -0
- package/landing-page/guidelines/Guidelines.md +61 -0
- package/landing-page/index.html +37 -0
- package/landing-page/package.json +90 -0
- package/landing-page/postcss.config.mjs +15 -0
- package/landing-page/public/_redirects +1 -0
- package/landing-page/public/favicon.png +0 -0
- package/landing-page/src/app/App.tsx +53 -0
- package/landing-page/src/app/components/figma/ImageWithFallback.tsx +27 -0
- package/landing-page/src/app/components/ui/accordion.tsx +66 -0
- package/landing-page/src/app/components/ui/alert-dialog.tsx +157 -0
- package/landing-page/src/app/components/ui/alert.tsx +66 -0
- package/landing-page/src/app/components/ui/aspect-ratio.tsx +11 -0
- package/landing-page/src/app/components/ui/avatar.tsx +53 -0
- package/landing-page/src/app/components/ui/badge.tsx +46 -0
- package/landing-page/src/app/components/ui/breadcrumb.tsx +109 -0
- package/landing-page/src/app/components/ui/button.tsx +57 -0
- package/landing-page/src/app/components/ui/calendar.tsx +75 -0
- package/landing-page/src/app/components/ui/card.tsx +92 -0
- package/landing-page/src/app/components/ui/carousel.tsx +241 -0
- package/landing-page/src/app/components/ui/chart.tsx +353 -0
- package/landing-page/src/app/components/ui/checkbox.tsx +32 -0
- package/landing-page/src/app/components/ui/collapsible.tsx +33 -0
- package/landing-page/src/app/components/ui/command.tsx +177 -0
- package/landing-page/src/app/components/ui/context-menu.tsx +252 -0
- package/landing-page/src/app/components/ui/dialog.tsx +135 -0
- package/landing-page/src/app/components/ui/drawer.tsx +132 -0
- package/landing-page/src/app/components/ui/dropdown-menu.tsx +257 -0
- package/landing-page/src/app/components/ui/form.tsx +168 -0
- package/landing-page/src/app/components/ui/hover-card.tsx +44 -0
- package/landing-page/src/app/components/ui/input-otp.tsx +77 -0
- package/landing-page/src/app/components/ui/input.tsx +21 -0
- package/landing-page/src/app/components/ui/label.tsx +24 -0
- package/landing-page/src/app/components/ui/menubar.tsx +276 -0
- package/landing-page/src/app/components/ui/navigation-menu.tsx +168 -0
- package/landing-page/src/app/components/ui/pagination.tsx +127 -0
- package/landing-page/src/app/components/ui/popover.tsx +48 -0
- package/landing-page/src/app/components/ui/progress.tsx +31 -0
- package/landing-page/src/app/components/ui/radio-group.tsx +45 -0
- package/landing-page/src/app/components/ui/resizable.tsx +56 -0
- package/landing-page/src/app/components/ui/scroll-area.tsx +58 -0
- package/landing-page/src/app/components/ui/select.tsx +189 -0
- package/landing-page/src/app/components/ui/separator.tsx +28 -0
- package/landing-page/src/app/components/ui/sheet.tsx +139 -0
- package/landing-page/src/app/components/ui/sidebar.tsx +726 -0
- package/landing-page/src/app/components/ui/skeleton.tsx +13 -0
- package/landing-page/src/app/components/ui/slider.tsx +63 -0
- package/landing-page/src/app/components/ui/sonner.tsx +25 -0
- package/landing-page/src/app/components/ui/switch.tsx +31 -0
- package/landing-page/src/app/components/ui/table.tsx +116 -0
- package/landing-page/src/app/components/ui/tabs.tsx +66 -0
- package/landing-page/src/app/components/ui/textarea.tsx +18 -0
- package/landing-page/src/app/components/ui/toggle-group.tsx +73 -0
- package/landing-page/src/app/components/ui/toggle.tsx +47 -0
- package/landing-page/src/app/components/ui/tooltip.tsx +61 -0
- package/landing-page/src/app/components/ui/use-mobile.ts +21 -0
- package/landing-page/src/app/components/ui/utils.ts +6 -0
- package/landing-page/src/components/docs/DocsContent.tsx +718 -0
- package/landing-page/src/components/docs/DocsSidebar.tsx +84 -0
- package/landing-page/src/components/landing/CTA.tsx +59 -0
- package/landing-page/src/components/landing/Comparison.tsx +84 -0
- package/landing-page/src/components/landing/FaultyTerminal.tsx +424 -0
- package/landing-page/src/components/landing/Features.tsx +201 -0
- package/landing-page/src/components/landing/Hero.tsx +142 -0
- package/landing-page/src/components/landing/Pricing.tsx +140 -0
- package/landing-page/src/components/landing/Roadmap.tsx +86 -0
- package/landing-page/src/components/landing/Security.tsx +81 -0
- package/landing-page/src/components/landing/TerminalWindow.tsx +27 -0
- package/landing-page/src/components/landing/UseCases.tsx +55 -0
- package/landing-page/src/components/landing/Workflow.tsx +101 -0
- package/landing-page/src/components/layout/DashboardNavbar.tsx +37 -0
- package/landing-page/src/components/layout/Footer.tsx +55 -0
- package/landing-page/src/components/layout/LandingNavbar.tsx +82 -0
- package/landing-page/src/components/ui/badge.tsx +39 -0
- package/landing-page/src/components/ui/breadcrumb.tsx +115 -0
- package/landing-page/src/components/ui/button.tsx +57 -0
- package/landing-page/src/components/ui/card.tsx +79 -0
- package/landing-page/src/components/ui/mock-terminal.tsx +68 -0
- package/landing-page/src/components/ui/separator.tsx +28 -0
- package/landing-page/src/lib/utils.ts +6 -0
- package/landing-page/src/main.tsx +10 -0
- package/landing-page/src/pages/Dashboard.tsx +133 -0
- package/landing-page/src/pages/DocsPage.tsx +79 -0
- package/landing-page/src/pages/LandingPage.tsx +31 -0
- package/landing-page/src/pages/TerminalView.tsx +106 -0
- package/landing-page/src/styles/fonts.css +0 -0
- package/landing-page/src/styles/index.css +3 -0
- package/landing-page/src/styles/tailwind.css +4 -0
- package/landing-page/src/styles/theme.css +181 -0
- package/landing-page/vite.config.ts +19 -0
- package/npm/darwin-arm64/bin/gssh +0 -0
- package/npm/darwin-arm64/package.json +20 -0
- package/package.json +74 -0
- package/scripts/build.ts +284 -0
- package/scripts/release.ts +140 -0
- package/src/__tests__/test-utils.ts +298 -0
- package/src/commands/__tests__/serve-messages.test.ts +190 -0
- package/src/commands/access.ts +298 -0
- package/src/commands/add.ts +452 -0
- package/src/commands/auth.ts +364 -0
- package/src/commands/connect.ts +287 -0
- package/src/commands/directory.ts +16 -0
- package/src/commands/host.ts +396 -0
- package/src/commands/identity.ts +184 -0
- package/src/commands/list.ts +200 -0
- package/src/commands/relay.ts +315 -0
- package/src/commands/remove.ts +241 -0
- package/src/commands/serve.ts +1493 -0
- package/src/commands/share.ts +456 -0
- package/src/commands/status.ts +125 -0
- package/src/commands/switch.ts +353 -0
- package/src/commands/tmux.ts +317 -0
- package/src/core/__tests__/access.test.ts +240 -0
- package/src/core/access.ts +277 -0
- package/src/core/bundle.ts +342 -0
- package/src/core/config.ts +510 -0
- package/src/core/git.ts +317 -0
- package/src/core/github.ts +151 -0
- package/src/core/identity.ts +631 -0
- package/src/core/linear.ts +225 -0
- package/src/core/shell.ts +161 -0
- package/src/core/trusted-relays.ts +315 -0
- package/src/index.ts +821 -0
- package/src/lib/remote-session/index.ts +7 -0
- package/src/lib/remote-session/protocol.ts +267 -0
- package/src/lib/remote-session/session-handler.ts +581 -0
- package/src/lib/remote-session/workspace-scanner.ts +167 -0
- package/src/lib/tmux-lite/README.md +81 -0
- package/src/lib/tmux-lite/cli.ts +796 -0
- package/src/lib/tmux-lite/crypto/__tests__/helpers/handshake-runner.ts +349 -0
- package/src/lib/tmux-lite/crypto/__tests__/helpers/mock-relay.ts +291 -0
- package/src/lib/tmux-lite/crypto/__tests__/helpers/test-identities.ts +142 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/authorization.integration.test.ts +339 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/e2e-communication.integration.test.ts +477 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/error-handling.integration.test.ts +499 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/handshake.integration.test.ts +371 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/security.integration.test.ts +573 -0
- package/src/lib/tmux-lite/crypto/access-control.test.ts +512 -0
- package/src/lib/tmux-lite/crypto/access-control.ts +320 -0
- package/src/lib/tmux-lite/crypto/frames.test.ts +262 -0
- package/src/lib/tmux-lite/crypto/frames.ts +141 -0
- package/src/lib/tmux-lite/crypto/handshake.ts +894 -0
- package/src/lib/tmux-lite/crypto/identity.test.ts +220 -0
- package/src/lib/tmux-lite/crypto/identity.ts +286 -0
- package/src/lib/tmux-lite/crypto/index.ts +51 -0
- package/src/lib/tmux-lite/crypto/invites.test.ts +381 -0
- package/src/lib/tmux-lite/crypto/invites.ts +215 -0
- package/src/lib/tmux-lite/crypto/keyexchange.ts +435 -0
- package/src/lib/tmux-lite/crypto/keys.test.ts +58 -0
- package/src/lib/tmux-lite/crypto/keys.ts +47 -0
- package/src/lib/tmux-lite/crypto/secretbox.test.ts +169 -0
- package/src/lib/tmux-lite/crypto/secretbox.ts +124 -0
- package/src/lib/tmux-lite/handshake-handler.ts +451 -0
- package/src/lib/tmux-lite/protocol.test.ts +307 -0
- package/src/lib/tmux-lite/protocol.ts +266 -0
- package/src/lib/tmux-lite/relay-client.ts +506 -0
- package/src/lib/tmux-lite/server.ts +1250 -0
- package/src/lib/tmux-lite/shell-integration.sh +37 -0
- package/src/lib/tmux-lite/terminal-queries.test.ts +54 -0
- package/src/lib/tmux-lite/terminal-queries.ts +49 -0
- package/src/relay/__tests__/e2e-flow.test.ts +1284 -0
- package/src/relay/__tests__/helpers/auth.ts +354 -0
- package/src/relay/__tests__/helpers/ports.ts +51 -0
- package/src/relay/__tests__/protocol-validation.test.ts +265 -0
- package/src/relay/authorization.ts +303 -0
- package/src/relay/embedded-assets.generated.d.ts +15 -0
- package/src/relay/identity.ts +352 -0
- package/src/relay/index.ts +57 -0
- package/src/relay/pipes.test.ts +427 -0
- package/src/relay/pipes.ts +195 -0
- package/src/relay/protocol.ts +804 -0
- package/src/relay/registries.test.ts +437 -0
- package/src/relay/registries.ts +593 -0
- package/src/relay/server.test.ts +1323 -0
- package/src/relay/server.ts +1092 -0
- package/src/relay/signing.ts +238 -0
- package/src/relay/types.ts +69 -0
- package/src/serve/client-session-manager.ts +622 -0
- package/src/serve/daemon.ts +497 -0
- package/src/serve/pty-session.ts +236 -0
- package/src/serve/types.ts +169 -0
- package/src/shared/components/Flow.tsx +453 -0
- package/src/shared/components/Flow.tui.tsx +343 -0
- package/src/shared/components/Flow.web.tsx +442 -0
- package/src/shared/components/Inbox.tsx +446 -0
- package/src/shared/components/Inbox.tui.tsx +262 -0
- package/src/shared/components/Inbox.web.tsx +329 -0
- package/src/shared/components/MachineList.tsx +187 -0
- package/src/shared/components/MachineList.tui.tsx +161 -0
- package/src/shared/components/MachineList.web.tsx +210 -0
- package/src/shared/components/ProjectList.tsx +176 -0
- package/src/shared/components/ProjectList.tui.tsx +109 -0
- package/src/shared/components/ProjectList.web.tsx +143 -0
- package/src/shared/components/SpacesBrowser.tsx +332 -0
- package/src/shared/components/SpacesBrowser.tui.tsx +163 -0
- package/src/shared/components/SpacesBrowser.web.tsx +221 -0
- package/src/shared/components/index.ts +103 -0
- package/src/shared/hooks/index.ts +16 -0
- package/src/shared/hooks/useNavigation.ts +226 -0
- package/src/shared/index.ts +122 -0
- package/src/shared/providers/LocalMachineProvider.ts +425 -0
- package/src/shared/providers/MachineProvider.ts +165 -0
- package/src/shared/providers/RemoteMachineProvider.ts +444 -0
- package/src/shared/providers/index.ts +26 -0
- package/src/shared/types.ts +145 -0
- package/src/tui/adapters.ts +120 -0
- package/src/tui/app.tsx +1816 -0
- package/src/tui/components/Terminal.tsx +580 -0
- package/src/tui/hooks/index.ts +35 -0
- package/src/tui/hooks/useAppState.ts +314 -0
- package/src/tui/hooks/useDaemonStatus.ts +174 -0
- package/src/tui/hooks/useInboxTUI.ts +113 -0
- package/src/tui/hooks/useRemoteMachines.ts +209 -0
- package/src/tui/index.ts +24 -0
- package/src/tui/state.ts +299 -0
- package/src/tui/terminal-bracketed-paste.test.ts +45 -0
- package/src/tui/terminal-bracketed-paste.ts +47 -0
- package/src/types/bundle.ts +112 -0
- package/src/types/config.ts +89 -0
- package/src/types/errors.ts +206 -0
- package/src/types/identity.ts +284 -0
- package/src/types/workspace-fuzzy.ts +49 -0
- package/src/types/workspace.ts +151 -0
- package/src/utils/bun-socket-writer.ts +80 -0
- package/src/utils/deps.ts +127 -0
- package/src/utils/fuzzy-match.ts +125 -0
- package/src/utils/logger.ts +127 -0
- package/src/utils/markdown.ts +254 -0
- package/src/utils/onboarding.ts +229 -0
- package/src/utils/prompts.ts +114 -0
- package/src/utils/run-commands.ts +112 -0
- package/src/utils/run-scripts.ts +142 -0
- package/src/utils/sanitize.ts +98 -0
- package/src/utils/secrets.ts +122 -0
- package/src/utils/shell-escape.ts +40 -0
- package/src/utils/utf8.ts +79 -0
- package/src/utils/workspace-state.ts +47 -0
- package/src/web/README.md +73 -0
- package/src/web/bun.lock +575 -0
- package/src/web/eslint.config.js +23 -0
- package/src/web/index.html +16 -0
- package/src/web/package.json +37 -0
- package/src/web/public/vite.svg +1 -0
- package/src/web/src/App.tsx +604 -0
- package/src/web/src/assets/react.svg +1 -0
- package/src/web/src/components/Terminal.tsx +207 -0
- package/src/web/src/hooks/useRelayConnection.ts +224 -0
- package/src/web/src/hooks/useTerminal.ts +699 -0
- package/src/web/src/index.css +55 -0
- package/src/web/src/lib/crypto/__tests__/web-terminal.test.ts +1158 -0
- package/src/web/src/lib/crypto/frames.ts +205 -0
- package/src/web/src/lib/crypto/handshake.ts +396 -0
- package/src/web/src/lib/crypto/identity.ts +128 -0
- package/src/web/src/lib/crypto/keyexchange.ts +246 -0
- package/src/web/src/lib/crypto/relay-signing.ts +53 -0
- package/src/web/src/lib/invite.ts +58 -0
- package/src/web/src/lib/storage/identity-store.ts +94 -0
- package/src/web/src/main.tsx +10 -0
- package/src/web/src/types/identity.ts +45 -0
- package/src/web/tsconfig.app.json +28 -0
- package/src/web/tsconfig.json +7 -0
- package/src/web/tsconfig.node.json +26 -0
- package/src/web/vite.config.ts +31 -0
- package/todo-security.md +92 -0
- package/tsconfig.json +23 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-shm +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-wal +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-shm +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-wal +0 -0
- package/worker/bun.lock +237 -0
- package/worker/package.json +22 -0
- package/worker/schema.sql +96 -0
- package/worker/src/handlers/auth.ts +451 -0
- package/worker/src/handlers/subdomains.ts +376 -0
- package/worker/src/handlers/user.ts +98 -0
- package/worker/src/index.ts +70 -0
- package/worker/src/middleware/auth.ts +152 -0
- package/worker/src/services/cloudflare.ts +609 -0
- package/worker/src/types.ts +96 -0
- package/worker/tsconfig.json +15 -0
- package/worker/wrangler.toml +26 -0
|
@@ -0,0 +1,1347 @@
|
|
|
1
|
+
# Gitspace Infrastructure Architecture
|
|
2
|
+
|
|
3
|
+
> **⚠️ FUTURE VISION DOCUMENT**
|
|
4
|
+
>
|
|
5
|
+
> This document describes a **planned future architecture** that is **not yet implemented**.
|
|
6
|
+
> The current implementation uses a simpler model with direct PTY sessions and WebSocket relay.
|
|
7
|
+
> See [RELAY.md](./RELAY.md) and [GETTING-STARTED.md](./GETTING-STARTED.md) for the current implementation.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
This document describes the infrastructure architecture for gitspace.sh - a platform for running development environments, CI/CD runners, and preview deployments using Firecracker microVMs.
|
|
12
|
+
|
|
13
|
+
## Table of Contents
|
|
14
|
+
|
|
15
|
+
1. [Vision](#vision)
|
|
16
|
+
2. [Architecture Overview](#architecture-overview)
|
|
17
|
+
3. [Orchestration Options](#orchestration-options)
|
|
18
|
+
4. [Flintlock: MicroVM Management](#flintlock-microvm-management)
|
|
19
|
+
5. [Nomad: Cluster Orchestration](#nomad-cluster-orchestration)
|
|
20
|
+
6. [Firecracker: The MicroVM Runtime](#firecracker-the-microvm-runtime)
|
|
21
|
+
7. [Image Build Pipeline](#image-build-pipeline)
|
|
22
|
+
8. [Storage Model](#storage-model)
|
|
23
|
+
9. [Networking Model](#networking-model)
|
|
24
|
+
10. [Cloudflare Tunnels: Public Ingress](#cloudflare-tunnels-public-ingress)
|
|
25
|
+
11. [GCP Integration](#gcp-integration)
|
|
26
|
+
12. [Local Development: Mac Parity](#local-development-mac-parity)
|
|
27
|
+
13. [Components to Build](#components-to-build)
|
|
28
|
+
14. [Decision Matrix](#decision-matrix)
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Vision
|
|
33
|
+
|
|
34
|
+
Gitspaces are lightweight, isolated environments that can be used for:
|
|
35
|
+
|
|
36
|
+
| Use Case | Description | Lifecycle |
|
|
37
|
+
|----------|-------------|-----------|
|
|
38
|
+
| **Dev Environment** | Interactive terminal access to a workspace | Long-running, persistent storage |
|
|
39
|
+
| **CI Runner** | Execute tests/builds on push/PR | Ephemeral, dies after job |
|
|
40
|
+
| **Preview Environment** | Run app for PR review | Medium-lived, public URL |
|
|
41
|
+
|
|
42
|
+
**Key Goals:**
|
|
43
|
+
- Scale to zero when idle ($0 cost)
|
|
44
|
+
- Use spot/preemptible instances for 60-90% cost savings
|
|
45
|
+
- Same environment from local dev → CI → preview
|
|
46
|
+
- E2E encrypted terminal access via gitspace.sh relay
|
|
47
|
+
- Simple: just write scripts, no GitHub Actions YAML ceremony
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Architecture Overview
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
55
|
+
│ USER LAYER │
|
|
56
|
+
│ │
|
|
57
|
+
│ spaces CLI ─────► Terminal access (E2E encrypted) │
|
|
58
|
+
│ Browser ─────────► Preview URLs (https://pr-123.preview.gitspace.sh) │
|
|
59
|
+
│ GitHub ──────────► Webhooks (push, PR events) │
|
|
60
|
+
└──────────────────────────────────────────────┬──────────────────────────────┘
|
|
61
|
+
│
|
|
62
|
+
▼
|
|
63
|
+
┌──────────────────────────────────────────────────────────────────────────────┐
|
|
64
|
+
│ CONTROL PLANE │
|
|
65
|
+
│ │
|
|
66
|
+
│ gitspace.sh relay │
|
|
67
|
+
│ ├── User authentication (API keys) │
|
|
68
|
+
│ ├── WebSocket relay (E2E encrypted terminal streams) │
|
|
69
|
+
│ ├── GitHub webhook handler │
|
|
70
|
+
│ ├── Job scheduler (submits to Nomad or custom) │
|
|
71
|
+
│ ├── Host provisioner (GCP API for scale up/down) │
|
|
72
|
+
│ └── State database (Postgres) │
|
|
73
|
+
│ │
|
|
74
|
+
│ Nomad Server (optional, for multi-host) │
|
|
75
|
+
│ ├── Job scheduling and bin packing │
|
|
76
|
+
│ ├── Health monitoring │
|
|
77
|
+
│ └── Cluster state │
|
|
78
|
+
└──────────────────────────────────────────────┬───────────────────────────────┘
|
|
79
|
+
│
|
|
80
|
+
▼
|
|
81
|
+
┌──────────────────────────────────────────────────────────────────────────────┐
|
|
82
|
+
│ DATA PLANE (Hosts) │
|
|
83
|
+
│ │
|
|
84
|
+
│ Each host runs: │
|
|
85
|
+
│ ├── Nomad Client (receives jobs, reports capacity) │
|
|
86
|
+
│ ├── Flintlock (manages Firecracker VMs via gRPC) │
|
|
87
|
+
│ ├── containerd (pulls OCI images) │
|
|
88
|
+
│ ├── cloudflared (tunnels for preview URLs) │
|
|
89
|
+
│ └── Firecracker VMs (the actual gitspaces) │
|
|
90
|
+
│ │
|
|
91
|
+
│ Hosts can be: │
|
|
92
|
+
│ ├── GCP Spot VMs (cheap, can be preempted) │
|
|
93
|
+
│ ├── GCP On-Demand VMs (reliable, more expensive) │
|
|
94
|
+
│ ├── Latitude.sh Bare Metal (dedicated, hourly billing) │
|
|
95
|
+
│ ├── Your own computer (self-hosted) │
|
|
96
|
+
│ └── Mac with Lima (local development) │
|
|
97
|
+
└──────────────────────────────────────────────────────────────────────────────┘
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Orchestration Options
|
|
103
|
+
|
|
104
|
+
We have three main options for orchestrating gitspaces across hosts:
|
|
105
|
+
|
|
106
|
+
### Option A: Custom Orchestration (DIY)
|
|
107
|
+
|
|
108
|
+
Build our own scheduler in the relay.
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
112
|
+
│ gitspace.sh relay │
|
|
113
|
+
│ │
|
|
114
|
+
│ ┌────────────────────────────────────────────────────────────────────────┐ │
|
|
115
|
+
│ │ Custom Scheduler │ │
|
|
116
|
+
│ │ │ │
|
|
117
|
+
│ │ - Host registry (which hosts are available) │ │
|
|
118
|
+
│ │ - Capacity tracking (CPU/mem per host) │ │
|
|
119
|
+
│ │ - Simple scheduling (round-robin with capacity check) │ │
|
|
120
|
+
│ │ - Job queue (in-memory or Redis) │ │
|
|
121
|
+
│ └────────────────────────────────────────────────────────────────────────┘ │
|
|
122
|
+
│ │
|
|
123
|
+
│ Talks directly to gitspace-daemon on each host via WebSocket/gRPC │
|
|
124
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
125
|
+
│
|
|
126
|
+
▼
|
|
127
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
128
|
+
│ gitspace-daemon (runs on each host) │
|
|
129
|
+
│ │
|
|
130
|
+
│ - Manages Firecracker VMs directly (or via Flintlock) │
|
|
131
|
+
│ - Reports capacity to relay │
|
|
132
|
+
│ - Handles volume management │
|
|
133
|
+
│ - Manages Cloudflare tunnels │
|
|
134
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Pros:**
|
|
138
|
+
- Full control
|
|
139
|
+
- No external dependencies
|
|
140
|
+
- Simpler for single-host
|
|
141
|
+
- Fun to build
|
|
142
|
+
|
|
143
|
+
**Cons:**
|
|
144
|
+
- Must build scheduling, health checks, failover
|
|
145
|
+
- More code to maintain
|
|
146
|
+
- Harder to get right at scale
|
|
147
|
+
|
|
148
|
+
**Best for:** Starting out, single host, learning
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
### Option B: Flintlock + Nomad
|
|
153
|
+
|
|
154
|
+
Use Flintlock for VM lifecycle, Nomad for scheduling.
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
158
|
+
│ gitspace.sh relay │
|
|
159
|
+
│ │
|
|
160
|
+
│ - Submits jobs to Nomad │
|
|
161
|
+
│ - Manages GCP hosts (scale up/down) │
|
|
162
|
+
│ - WebSocket relay for terminal access │
|
|
163
|
+
└──────────────────────────────────────────────┬──────────────────────────────┘
|
|
164
|
+
│
|
|
165
|
+
▼
|
|
166
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
167
|
+
│ Nomad Cluster │
|
|
168
|
+
│ │
|
|
169
|
+
│ Server: Scheduling, state, health monitoring │
|
|
170
|
+
│ Client: Runs on each host, executes jobs │
|
|
171
|
+
└──────────────────────────────────────────────┬──────────────────────────────┘
|
|
172
|
+
│
|
|
173
|
+
▼
|
|
174
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
175
|
+
│ Each Host │
|
|
176
|
+
│ │
|
|
177
|
+
│ Nomad Client ──► Flintlock ──► Firecracker VMs │
|
|
178
|
+
│ │ │
|
|
179
|
+
│ ▼ │
|
|
180
|
+
│ containerd (OCI images) │
|
|
181
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Pros:**
|
|
185
|
+
- Battle-tested scheduling (Nomad)
|
|
186
|
+
- Clean VM lifecycle management (Flintlock)
|
|
187
|
+
- OCI images for everything (containerd)
|
|
188
|
+
- Health checks, restarts, bin packing built-in
|
|
189
|
+
|
|
190
|
+
**Cons:**
|
|
191
|
+
- More moving parts
|
|
192
|
+
- Flintlock is community-maintained (was Weaveworks)
|
|
193
|
+
- Learning curve for Nomad
|
|
194
|
+
|
|
195
|
+
**Best for:** Multi-host production, scaling
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
### Option C: Nomad Only (with raw_exec or custom driver)
|
|
200
|
+
|
|
201
|
+
Use Nomad for scheduling, manage Firecracker directly.
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
205
|
+
│ Nomad Cluster │
|
|
206
|
+
│ │
|
|
207
|
+
│ Jobs use raw_exec or firecracker-task-driver to run VMs │
|
|
208
|
+
└──────────────────────────────────────────────┬──────────────────────────────┘
|
|
209
|
+
│
|
|
210
|
+
▼
|
|
211
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
212
|
+
│ Each Host │
|
|
213
|
+
│ │
|
|
214
|
+
│ Nomad Client ──► raw_exec ──► firecracker binary │
|
|
215
|
+
│ │ │
|
|
216
|
+
│ ▼ │
|
|
217
|
+
│ Firecracker VMs │
|
|
218
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**Pros:**
|
|
222
|
+
- Nomad handles scheduling
|
|
223
|
+
- Fewer components than Flintlock
|
|
224
|
+
- Community firecracker-task-driver available
|
|
225
|
+
|
|
226
|
+
**Cons:**
|
|
227
|
+
- Must manage images ourselves (no containerd integration)
|
|
228
|
+
- firecracker-task-driver is also community-maintained
|
|
229
|
+
- More manual VM configuration
|
|
230
|
+
|
|
231
|
+
**Best for:** Middle ground, if Flintlock feels too heavy
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Flintlock: MicroVM Management
|
|
236
|
+
|
|
237
|
+
[Flintlock](https://github.com/liquidmetal-dev/flintlock) is a gRPC service for managing Firecracker/Cloud Hypervisor VMs on a single host.
|
|
238
|
+
|
|
239
|
+
### What Flintlock Does
|
|
240
|
+
|
|
241
|
+
| Capability | Description |
|
|
242
|
+
|------------|-------------|
|
|
243
|
+
| **VM Lifecycle** | Create, start, stop, delete microVMs |
|
|
244
|
+
| **OCI Images** | Pull kernel and rootfs from container registries |
|
|
245
|
+
| **containerd Integration** | Uses containerd for image management and snapshots |
|
|
246
|
+
| **Networking** | Configures TAP devices, CNI plugins |
|
|
247
|
+
| **Metadata** | Injects cloud-init/ignition for VM configuration |
|
|
248
|
+
| **gRPC API** | Clean, well-defined API for all operations |
|
|
249
|
+
|
|
250
|
+
### What Flintlock Does NOT Do
|
|
251
|
+
|
|
252
|
+
| Capability | Needs |
|
|
253
|
+
|------------|-------|
|
|
254
|
+
| Multi-host scheduling | Nomad or custom |
|
|
255
|
+
| Scale up/down hosts | Custom + cloud API |
|
|
256
|
+
| Cross-host networking | CNI/Tailscale/Cloudflare |
|
|
257
|
+
| Persistent volumes across hosts | Custom |
|
|
258
|
+
|
|
259
|
+
### Flintlock Architecture
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
263
|
+
│ HOST │
|
|
264
|
+
│ │
|
|
265
|
+
│ ┌─────────────────────────────────────────────────────────────────────────┐│
|
|
266
|
+
│ │ flintlockd ││
|
|
267
|
+
│ │ ││
|
|
268
|
+
│ │ gRPC API (:9090) ││
|
|
269
|
+
│ │ ├── CreateMicroVM(spec) ││
|
|
270
|
+
│ │ ├── DeleteMicroVM(id) ││
|
|
271
|
+
│ │ ├── GetMicroVM(id) ││
|
|
272
|
+
│ │ └── ListMicroVMs() ││
|
|
273
|
+
│ │ ││
|
|
274
|
+
│ │ Uses containerd for: ││
|
|
275
|
+
│ │ ├── Pulling OCI images (kernel, rootfs) ││
|
|
276
|
+
│ │ └── Managing devicemapper snapshots ││
|
|
277
|
+
│ └─────────────────────────────────────────────────────────────────────────┘│
|
|
278
|
+
│ │ │
|
|
279
|
+
│ ┌───────────────────┼───────────────────┐ │
|
|
280
|
+
│ ▼ ▼ ▼ │
|
|
281
|
+
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
282
|
+
│ │ Firecracker │ │ Firecracker │ │ Firecracker │ │
|
|
283
|
+
│ │ MicroVM │ │ MicroVM │ │ MicroVM │ │
|
|
284
|
+
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|
285
|
+
│ │
|
|
286
|
+
└──────────────────────────────────────────────────────────────────────────────┘
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Flintlock MicroVM Spec
|
|
290
|
+
|
|
291
|
+
```json
|
|
292
|
+
{
|
|
293
|
+
"id": "gitspace-abc123",
|
|
294
|
+
"namespace": "gitspaces",
|
|
295
|
+
"labels": {
|
|
296
|
+
"user_id": "user-xyz",
|
|
297
|
+
"workspace": "my-project"
|
|
298
|
+
},
|
|
299
|
+
"vcpu": 2,
|
|
300
|
+
"memory_in_mb": 2048,
|
|
301
|
+
"kernel": {
|
|
302
|
+
"image": "ghcr.io/gitspace/kernel:5.10",
|
|
303
|
+
"filename": "vmlinux"
|
|
304
|
+
},
|
|
305
|
+
"root_volume": {
|
|
306
|
+
"id": "root",
|
|
307
|
+
"is_read_only": false,
|
|
308
|
+
"source": {
|
|
309
|
+
"container_source": "ghcr.io/gitspace/ubuntu:22.04"
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
"additional_volumes": [
|
|
313
|
+
{
|
|
314
|
+
"id": "workspace",
|
|
315
|
+
"is_read_only": false,
|
|
316
|
+
"mount_point": "/workspace",
|
|
317
|
+
"source": {
|
|
318
|
+
"host_path": "/var/lib/gitspace/volumes/user-xyz/my-project"
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
],
|
|
322
|
+
"interfaces": [
|
|
323
|
+
{
|
|
324
|
+
"device_id": "eth0",
|
|
325
|
+
"type": "TAP"
|
|
326
|
+
}
|
|
327
|
+
],
|
|
328
|
+
"metadata": {
|
|
329
|
+
"user-data": "<base64-encoded-cloud-init>"
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Flintlock Configuration
|
|
335
|
+
|
|
336
|
+
```yaml
|
|
337
|
+
# /etc/flintlock/config.yaml
|
|
338
|
+
|
|
339
|
+
containerd-socket: /run/containerd/containerd.sock
|
|
340
|
+
grpc-endpoint: 0.0.0.0:9090
|
|
341
|
+
state-dir: /var/lib/flintlock/vm
|
|
342
|
+
default-vmm: firecracker
|
|
343
|
+
firecracker-bin: /usr/local/bin/firecracker
|
|
344
|
+
bridge-name: flbr0
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Nomad: Cluster Orchestration
|
|
350
|
+
|
|
351
|
+
[Nomad](https://www.nomadproject.io/) is HashiCorp's workload orchestrator. Simpler than Kubernetes, supports VMs and other non-container workloads.
|
|
352
|
+
|
|
353
|
+
### Nomad Architecture
|
|
354
|
+
|
|
355
|
+
```
|
|
356
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
357
|
+
│ Nomad Server Cluster │
|
|
358
|
+
│ │
|
|
359
|
+
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
|
|
360
|
+
│ │ Nomad Server │ │ Nomad Server │ │ Nomad Server │ │
|
|
361
|
+
│ │ (Leader) │◄─┤ (Follower) │◄─┤ (Follower) │ │
|
|
362
|
+
│ └───────────────┘ └───────────────┘ └───────────────┘ │
|
|
363
|
+
│ │
|
|
364
|
+
│ Responsibilities: │
|
|
365
|
+
│ ├── Leader election (Raft consensus) │
|
|
366
|
+
│ ├── Job scheduling and placement │
|
|
367
|
+
│ ├── Cluster state │
|
|
368
|
+
│ └── Health monitoring │
|
|
369
|
+
└──────────────────────────────────────────────┬──────────────────────────────┘
|
|
370
|
+
│
|
|
371
|
+
┌────────────────────────────────┼────────────────────────────────┐
|
|
372
|
+
│ │ │
|
|
373
|
+
▼ ▼ ▼
|
|
374
|
+
┌───────────────────────┐ ┌───────────────────────┐ ┌───────────────────────┐
|
|
375
|
+
│ Nomad Client │ │ Nomad Client │ │ Nomad Client │
|
|
376
|
+
│ (Host 1) │ │ (Host 2) │ │ (Host 3) │
|
|
377
|
+
│ │ │ │ │ │
|
|
378
|
+
│ - Receives jobs │ │ - Receives jobs │ │ - Receives jobs │
|
|
379
|
+
│ - Reports capacity │ │ - Reports capacity │ │ - Reports capacity │
|
|
380
|
+
│ - Runs task drivers │ │ - Runs task drivers │ │ - Runs task drivers │
|
|
381
|
+
└───────────────────────┘ └───────────────────────┘ └───────────────────────┘
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Nomad Server Configuration
|
|
385
|
+
|
|
386
|
+
```hcl
|
|
387
|
+
# /etc/nomad.d/server.hcl
|
|
388
|
+
|
|
389
|
+
datacenter = "gcp-us-central1"
|
|
390
|
+
data_dir = "/opt/nomad/data"
|
|
391
|
+
|
|
392
|
+
server {
|
|
393
|
+
enabled = true
|
|
394
|
+
bootstrap_expect = 3
|
|
395
|
+
encrypt = "GOSSIP_ENCRYPTION_KEY"
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
addresses {
|
|
399
|
+
http = "0.0.0.0"
|
|
400
|
+
rpc = "0.0.0.0"
|
|
401
|
+
serf = "0.0.0.0"
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
acl {
|
|
405
|
+
enabled = true
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Nomad Client Configuration
|
|
410
|
+
|
|
411
|
+
```hcl
|
|
412
|
+
# /etc/nomad.d/client.hcl
|
|
413
|
+
|
|
414
|
+
datacenter = "gcp-us-central1"
|
|
415
|
+
data_dir = "/opt/nomad/data"
|
|
416
|
+
|
|
417
|
+
client {
|
|
418
|
+
enabled = true
|
|
419
|
+
|
|
420
|
+
meta {
|
|
421
|
+
"zone" = "us-central1-a"
|
|
422
|
+
"spot" = "true"
|
|
423
|
+
"has_flintlock" = "true"
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
host_volume "gitspace-volumes" {
|
|
427
|
+
path = "/var/lib/gitspace/volumes"
|
|
428
|
+
read_only = false
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
servers = ["nomad-server.internal:4647"]
|
|
433
|
+
|
|
434
|
+
plugin "raw_exec" {
|
|
435
|
+
config {
|
|
436
|
+
enabled = true
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Nomad Job Types
|
|
442
|
+
|
|
443
|
+
**Service Jobs** (long-running):
|
|
444
|
+
```hcl
|
|
445
|
+
job "gitspace-dev" {
|
|
446
|
+
type = "service"
|
|
447
|
+
# ... runs until stopped
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
**Batch Jobs** (run to completion):
|
|
452
|
+
```hcl
|
|
453
|
+
job "gitspace-ci" {
|
|
454
|
+
type = "batch"
|
|
455
|
+
# ... runs until exit, with timeout
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
**Parameterized Jobs** (templates):
|
|
460
|
+
```hcl
|
|
461
|
+
job "gitspace" {
|
|
462
|
+
type = "service"
|
|
463
|
+
|
|
464
|
+
parameterized {
|
|
465
|
+
meta_required = ["user_id", "workspace_id"]
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
# Each dispatch creates a child job
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### Example: Gitspace Service Job
|
|
473
|
+
|
|
474
|
+
```hcl
|
|
475
|
+
job "gitspace" {
|
|
476
|
+
type = "service"
|
|
477
|
+
datacenters = ["gcp-us-central1"]
|
|
478
|
+
|
|
479
|
+
parameterized {
|
|
480
|
+
meta_required = ["user_id", "workspace_id", "workspace_name"]
|
|
481
|
+
meta_optional = ["cpu", "memory", "zone"]
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
group "microvm" {
|
|
485
|
+
count = 1
|
|
486
|
+
|
|
487
|
+
constraint {
|
|
488
|
+
attribute = "${meta.has_flintlock}"
|
|
489
|
+
value = "true"
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
volume "workspace" {
|
|
493
|
+
type = "host"
|
|
494
|
+
source = "gitspace-volumes"
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
task "vm" {
|
|
498
|
+
driver = "raw_exec"
|
|
499
|
+
|
|
500
|
+
config {
|
|
501
|
+
command = "/usr/local/bin/flintlock-ctl"
|
|
502
|
+
args = ["microvm", "create", "--json-spec", "${NOMAD_TASK_DIR}/spec.json"]
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
template {
|
|
506
|
+
destination = "local/spec.json"
|
|
507
|
+
data = <<EOF
|
|
508
|
+
{
|
|
509
|
+
"id": "{{ env "NOMAD_META_workspace_id" }}",
|
|
510
|
+
"namespace": "gitspaces",
|
|
511
|
+
"vcpu": {{ env "NOMAD_META_cpu" | default "2" }},
|
|
512
|
+
"memory_in_mb": {{ env "NOMAD_META_memory" | default "2048" }},
|
|
513
|
+
"kernel": {
|
|
514
|
+
"image": "ghcr.io/gitspace/kernel:5.10"
|
|
515
|
+
},
|
|
516
|
+
"root_volume": {
|
|
517
|
+
"source": {
|
|
518
|
+
"container_source": "ghcr.io/gitspace/ubuntu:22.04"
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
EOF
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
resources {
|
|
526
|
+
cpu = 2000
|
|
527
|
+
memory = 2048
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### Example: CI Batch Job
|
|
535
|
+
|
|
536
|
+
```hcl
|
|
537
|
+
job "ci" {
|
|
538
|
+
type = "batch"
|
|
539
|
+
datacenters = ["gcp-us-central1"]
|
|
540
|
+
|
|
541
|
+
parameterized {
|
|
542
|
+
meta_required = ["repo", "commit_sha", "job_id"]
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
group "runner" {
|
|
546
|
+
task "run" {
|
|
547
|
+
driver = "raw_exec"
|
|
548
|
+
|
|
549
|
+
config {
|
|
550
|
+
command = "/usr/local/bin/gitspace-ci-runner"
|
|
551
|
+
args = [
|
|
552
|
+
"--job-id", "${NOMAD_META_job_id}",
|
|
553
|
+
"--repo", "${NOMAD_META_repo}",
|
|
554
|
+
"--commit", "${NOMAD_META_commit_sha}",
|
|
555
|
+
]
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
resources {
|
|
559
|
+
cpu = 4000
|
|
560
|
+
memory = 8192
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
---
|
|
568
|
+
|
|
569
|
+
## Firecracker: The MicroVM Runtime
|
|
570
|
+
|
|
571
|
+
[Firecracker](https://firecracker-microvm.github.io/) is a lightweight VMM (Virtual Machine Monitor) designed for serverless and container workloads.
|
|
572
|
+
|
|
573
|
+
### Key Characteristics
|
|
574
|
+
|
|
575
|
+
| Property | Value |
|
|
576
|
+
|----------|-------|
|
|
577
|
+
| **Boot time** | <125ms |
|
|
578
|
+
| **Memory overhead** | <5 MiB per VM |
|
|
579
|
+
| **Creation rate** | Up to 150 VMs/second/host |
|
|
580
|
+
| **Isolation** | Hardware virtualization (KVM) |
|
|
581
|
+
| **Supported arch** | x86_64, aarch64 |
|
|
582
|
+
|
|
583
|
+
### Firecracker Configuration
|
|
584
|
+
|
|
585
|
+
```json
|
|
586
|
+
{
|
|
587
|
+
"boot-source": {
|
|
588
|
+
"kernel_image_path": "/var/lib/firecracker/vmlinux",
|
|
589
|
+
"boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
|
|
590
|
+
},
|
|
591
|
+
"drives": [
|
|
592
|
+
{
|
|
593
|
+
"drive_id": "rootfs",
|
|
594
|
+
"path_on_host": "/var/lib/firecracker/rootfs.ext4",
|
|
595
|
+
"is_root_device": true,
|
|
596
|
+
"is_read_only": false
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
"drive_id": "workspace",
|
|
600
|
+
"path_on_host": "/var/lib/gitspace/volumes/user-xyz/workspace.ext4",
|
|
601
|
+
"is_root_device": false,
|
|
602
|
+
"is_read_only": false
|
|
603
|
+
}
|
|
604
|
+
],
|
|
605
|
+
"machine-config": {
|
|
606
|
+
"vcpu_count": 2,
|
|
607
|
+
"mem_size_mib": 2048
|
|
608
|
+
},
|
|
609
|
+
"network-interfaces": [
|
|
610
|
+
{
|
|
611
|
+
"iface_id": "eth0",
|
|
612
|
+
"guest_mac": "AA:FC:00:00:00:01",
|
|
613
|
+
"host_dev_name": "tap0"
|
|
614
|
+
}
|
|
615
|
+
]
|
|
616
|
+
}
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
### Requirements
|
|
620
|
+
|
|
621
|
+
- Linux host with KVM enabled
|
|
622
|
+
- `/dev/kvm` accessible
|
|
623
|
+
- For nested virtualization (VMs inside VMs):
|
|
624
|
+
- GCP: `--enable-nested-virtualization` on n2 instances
|
|
625
|
+
- Mac: macOS 15+ with M2/M3 chip, Lima with `nestedVirtualization: true`
|
|
626
|
+
|
|
627
|
+
---
|
|
628
|
+
|
|
629
|
+
## Image Build Pipeline
|
|
630
|
+
|
|
631
|
+
We own the image pipeline to ensure parity across all environments.
|
|
632
|
+
|
|
633
|
+
### Pipeline Overview
|
|
634
|
+
|
|
635
|
+
```
|
|
636
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
637
|
+
│ IMAGE BUILD PIPELINE │
|
|
638
|
+
│ │
|
|
639
|
+
│ Dockerfile │
|
|
640
|
+
│ │ │
|
|
641
|
+
│ ▼ │
|
|
642
|
+
│ BuildKit (multi-arch: amd64, arm64) │
|
|
643
|
+
│ │ │
|
|
644
|
+
│ ▼ │
|
|
645
|
+
│ OCI Image │
|
|
646
|
+
│ │ │
|
|
647
|
+
│ ├──────────────────────────────────────┐ │
|
|
648
|
+
│ ▼ ▼ │
|
|
649
|
+
│ Push to Registry Convert to rootfs │
|
|
650
|
+
│ (ghcr.io/gitspace/...) (for direct Firecracker use) │
|
|
651
|
+
│ │ │ │
|
|
652
|
+
│ ▼ ▼ │
|
|
653
|
+
│ Flintlock pulls via containerd Manual FC config │
|
|
654
|
+
│ │
|
|
655
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
### Base Image Dockerfile
|
|
659
|
+
|
|
660
|
+
```dockerfile
|
|
661
|
+
# images/ubuntu-base/Dockerfile
|
|
662
|
+
|
|
663
|
+
FROM ubuntu:22.04
|
|
664
|
+
|
|
665
|
+
# System packages
|
|
666
|
+
RUN apt-get update && apt-get install -y \
|
|
667
|
+
curl \
|
|
668
|
+
git \
|
|
669
|
+
build-essential \
|
|
670
|
+
sudo \
|
|
671
|
+
openssh-server \
|
|
672
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
673
|
+
|
|
674
|
+
# Create gitspace user
|
|
675
|
+
RUN useradd -m -s /bin/bash gitspace && \
|
|
676
|
+
echo "gitspace ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
|
|
677
|
+
|
|
678
|
+
# Install common dev tools
|
|
679
|
+
RUN curl -fsSL https://bun.sh/install | bash
|
|
680
|
+
RUN curl -fsSL https://get.docker.com | bash
|
|
681
|
+
|
|
682
|
+
# tmux-lite-server for terminal access
|
|
683
|
+
COPY --from=gitspace/tmux-lite:latest /usr/local/bin/tmux-lite-server /usr/local/bin/
|
|
684
|
+
|
|
685
|
+
# Cloud-init for configuration
|
|
686
|
+
RUN apt-get update && apt-get install -y cloud-init
|
|
687
|
+
|
|
688
|
+
# Startup script
|
|
689
|
+
COPY startup.sh /usr/local/bin/
|
|
690
|
+
RUN chmod +x /usr/local/bin/startup.sh
|
|
691
|
+
|
|
692
|
+
CMD ["/usr/local/bin/startup.sh"]
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
### Build Script
|
|
696
|
+
|
|
697
|
+
```bash
|
|
698
|
+
#!/bin/bash
|
|
699
|
+
# scripts/build-images.sh
|
|
700
|
+
|
|
701
|
+
set -e
|
|
702
|
+
|
|
703
|
+
REGISTRY="ghcr.io/gitspace"
|
|
704
|
+
VERSION="${1:-latest}"
|
|
705
|
+
|
|
706
|
+
# Build multi-arch
|
|
707
|
+
docker buildx build \
|
|
708
|
+
--platform linux/amd64,linux/arm64 \
|
|
709
|
+
--push \
|
|
710
|
+
-t "${REGISTRY}/ubuntu:${VERSION}" \
|
|
711
|
+
-f images/ubuntu-base/Dockerfile \
|
|
712
|
+
images/ubuntu-base/
|
|
713
|
+
|
|
714
|
+
# Build kernel image
|
|
715
|
+
docker buildx build \
|
|
716
|
+
--platform linux/amd64,linux/arm64 \
|
|
717
|
+
--push \
|
|
718
|
+
-t "${REGISTRY}/kernel:5.10" \
|
|
719
|
+
-f images/kernel/Dockerfile \
|
|
720
|
+
images/kernel/
|
|
721
|
+
|
|
722
|
+
echo "Images pushed to ${REGISTRY}"
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
### Converting OCI to rootfs (for direct Firecracker use)
|
|
726
|
+
|
|
727
|
+
```bash
|
|
728
|
+
#!/bin/bash
|
|
729
|
+
# scripts/oci-to-rootfs.sh
|
|
730
|
+
|
|
731
|
+
IMAGE="ghcr.io/gitspace/ubuntu:latest"
|
|
732
|
+
OUTPUT="rootfs.ext4"
|
|
733
|
+
SIZE_MB=4096
|
|
734
|
+
|
|
735
|
+
# Pull and extract
|
|
736
|
+
skopeo copy "docker://${IMAGE}" "oci:image:latest"
|
|
737
|
+
umoci unpack --image image:latest bundle
|
|
738
|
+
|
|
739
|
+
# Create ext4 filesystem
|
|
740
|
+
dd if=/dev/zero of="${OUTPUT}" bs=1M count="${SIZE_MB}"
|
|
741
|
+
mkfs.ext4 "${OUTPUT}"
|
|
742
|
+
|
|
743
|
+
# Mount and copy
|
|
744
|
+
mkdir -p /tmp/rootfs
|
|
745
|
+
mount -o loop "${OUTPUT}" /tmp/rootfs
|
|
746
|
+
cp -a bundle/rootfs/* /tmp/rootfs/
|
|
747
|
+
umount /tmp/rootfs
|
|
748
|
+
|
|
749
|
+
echo "Created ${OUTPUT}"
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
---
|
|
753
|
+
|
|
754
|
+
## Storage Model
|
|
755
|
+
|
|
756
|
+
### Overview
|
|
757
|
+
|
|
758
|
+
```
|
|
759
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
760
|
+
│ STORAGE MODEL │
|
|
761
|
+
│ │
|
|
762
|
+
│ Root Volume (ephemeral) │
|
|
763
|
+
│ ├── OCI image pulled by containerd │
|
|
764
|
+
│ ├── Read-write, but reset on VM restart │
|
|
765
|
+
│ └── Contains OS, tools, runtime │
|
|
766
|
+
│ │
|
|
767
|
+
│ Workspace Volume (persistent) │
|
|
768
|
+
│ ├── Sparse ext4 file on host │
|
|
769
|
+
│ ├── Attached as /dev/vdb, mounted at /workspace │
|
|
770
|
+
│ ├── Survives VM restarts │
|
|
771
|
+
│ └── User's code, data, config │
|
|
772
|
+
│ │
|
|
773
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
### Host Storage Layout
|
|
777
|
+
|
|
778
|
+
```
|
|
779
|
+
/var/lib/gitspace/
|
|
780
|
+
├── images/ # Cached rootfs images
|
|
781
|
+
│ ├── ubuntu-22.04.ext4
|
|
782
|
+
│ └── node-20.ext4
|
|
783
|
+
│
|
|
784
|
+
├── kernel/ # Kernel images
|
|
785
|
+
│ └── vmlinux-5.10
|
|
786
|
+
│
|
|
787
|
+
└── volumes/ # Persistent workspace volumes (btrfs)
|
|
788
|
+
├── user-abc/
|
|
789
|
+
│ ├── workspace-1.ext4 # Sparse file, grows on demand
|
|
790
|
+
│ └── workspace-2.ext4
|
|
791
|
+
└── user-def/
|
|
792
|
+
└── workspace-1.ext4
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
### Btrfs for Volume Management
|
|
796
|
+
|
|
797
|
+
```bash
|
|
798
|
+
# Initial setup
|
|
799
|
+
mkfs.btrfs /dev/sdb
|
|
800
|
+
mount /dev/sdb /var/lib/gitspace/volumes
|
|
801
|
+
|
|
802
|
+
# Create workspace with quota
|
|
803
|
+
btrfs subvolume create /var/lib/gitspace/volumes/@user-abc-ws-1
|
|
804
|
+
btrfs qgroup limit 20G /var/lib/gitspace/volumes/@user-abc-ws-1
|
|
805
|
+
|
|
806
|
+
# Create sparse ext4 file (100GB logical, ~30MB actual)
|
|
807
|
+
truncate -s 100G /var/lib/gitspace/volumes/@user-abc-ws-1/workspace.ext4
|
|
808
|
+
mkfs.ext4 -F workspace.ext4
|
|
809
|
+
|
|
810
|
+
# Snapshot for backup
|
|
811
|
+
btrfs subvolume snapshot \
|
|
812
|
+
/var/lib/gitspace/volumes/@user-abc-ws-1 \
|
|
813
|
+
/var/lib/gitspace/snapshots/@user-abc-ws-1-$(date +%Y%m%d)
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
### Storage Tiers
|
|
817
|
+
|
|
818
|
+
| Tier | Quota per Workspace | Max Workspaces |
|
|
819
|
+
|------|---------------------|----------------|
|
|
820
|
+
| Free | 5 GB | 2 |
|
|
821
|
+
| Pro | 20 GB | 10 |
|
|
822
|
+
| Team | 50 GB | 50 |
|
|
823
|
+
|
|
824
|
+
### GCP Persistent Disk Setup
|
|
825
|
+
|
|
826
|
+
```bash
|
|
827
|
+
# Create persistent disk
|
|
828
|
+
gcloud compute disks create gitspace-volumes \
|
|
829
|
+
--size=1TB \
|
|
830
|
+
--type=pd-ssd \
|
|
831
|
+
--zone=us-central1-a
|
|
832
|
+
|
|
833
|
+
# Attach to VM
|
|
834
|
+
gcloud compute instances attach-disk gitspace-host \
|
|
835
|
+
--disk=gitspace-volumes \
|
|
836
|
+
--zone=us-central1-a
|
|
837
|
+
|
|
838
|
+
# Inside VM: format as btrfs
|
|
839
|
+
mkfs.btrfs /dev/sdb
|
|
840
|
+
mount /dev/sdb /var/lib/gitspace/volumes
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
---
|
|
844
|
+
|
|
845
|
+
## Networking Model
|
|
846
|
+
|
|
847
|
+
### Overview
|
|
848
|
+
|
|
849
|
+
```
|
|
850
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
851
|
+
│ NETWORKING MODEL │
|
|
852
|
+
│ │
|
|
853
|
+
│ Terminal Access: │
|
|
854
|
+
│ └── gitspace.sh relay (WebSocket, E2E encrypted) │
|
|
855
|
+
│ └── User ◄──wss──► Relay ◄──wss──► tmux-lite-server in VM │
|
|
856
|
+
│ │
|
|
857
|
+
│ Preview URLs (Public HTTP): │
|
|
858
|
+
│ └── Cloudflare Tunnel (outbound only) │
|
|
859
|
+
│ └── User ◄──https──► Cloudflare ◄──tunnel──► App in VM │
|
|
860
|
+
│ │
|
|
861
|
+
│ VM Internet Access (Outbound): │
|
|
862
|
+
│ └── NAT via host (iptables masquerade) │
|
|
863
|
+
│ └── VM ──► TAP ──► Bridge ──► Host ──► Internet │
|
|
864
|
+
│ │
|
|
865
|
+
│ VM-to-VM (Same Host): │
|
|
866
|
+
│ └── Bridge network │
|
|
867
|
+
│ └── VM1 ◄──► Bridge ◄──► VM2 │
|
|
868
|
+
│ │
|
|
869
|
+
│ VM-to-VM (Cross Host): │
|
|
870
|
+
│ └── Not needed! Use public preview URLs │
|
|
871
|
+
│ └── Frontend calls https://api-pr-123.preview.gitspace.sh │
|
|
872
|
+
│ │
|
|
873
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
### TAP/Bridge Setup (per host)
|
|
877
|
+
|
|
878
|
+
```bash
|
|
879
|
+
# Create bridge
|
|
880
|
+
ip link add name flbr0 type bridge
|
|
881
|
+
ip addr add 10.100.0.1/24 dev flbr0
|
|
882
|
+
ip link set flbr0 up
|
|
883
|
+
|
|
884
|
+
# Enable IP forwarding
|
|
885
|
+
echo 1 > /proc/sys/net/ipv4/ip_forward
|
|
886
|
+
|
|
887
|
+
# NAT for outbound traffic
|
|
888
|
+
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
|
|
889
|
+
iptables -A FORWARD -i flbr0 -o eth0 -j ACCEPT
|
|
890
|
+
iptables -A FORWARD -i eth0 -o flbr0 -m state --state RELATED,ESTABLISHED -j ACCEPT
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
### Per-VM TAP Device
|
|
894
|
+
|
|
895
|
+
```bash
|
|
896
|
+
# Created by Flintlock automatically
|
|
897
|
+
ip tuntap add dev tap0 mode tap
|
|
898
|
+
ip link set tap0 master flbr0
|
|
899
|
+
ip link set tap0 up
|
|
900
|
+
|
|
901
|
+
# VM gets IP via DHCP or static config
|
|
902
|
+
# Inside VM: eth0 = 10.100.0.2/24, gateway = 10.100.0.1
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
---
|
|
906
|
+
|
|
907
|
+
## Cloudflare Tunnels: Public Ingress
|
|
908
|
+
|
|
909
|
+
### How It Works
|
|
910
|
+
|
|
911
|
+
```
|
|
912
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
913
|
+
│ CLOUDFLARE TUNNEL │
|
|
914
|
+
│ │
|
|
915
|
+
│ User Browser │
|
|
916
|
+
│ │ │
|
|
917
|
+
│ │ https://pr-123.preview.gitspace.sh │
|
|
918
|
+
│ ▼ │
|
|
919
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
920
|
+
│ │ Cloudflare Edge │ │
|
|
921
|
+
│ │ - SSL termination │ │
|
|
922
|
+
│ │ - DDoS protection │ │
|
|
923
|
+
│ │ - WAF │ │
|
|
924
|
+
│ │ - Routes by hostname │ │
|
|
925
|
+
│ └──────────────────────────────┬──────────────────────────────────────┘ │
|
|
926
|
+
│ │ │
|
|
927
|
+
│ │ Tunnel (outbound from origin) │
|
|
928
|
+
│ ▼ │
|
|
929
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
930
|
+
│ │ Host (gitspace-host-xyz) │ │
|
|
931
|
+
│ │ │ │
|
|
932
|
+
│ │ cloudflared ◄─────────────────────────────────────────────────────┤ │
|
|
933
|
+
│ │ │ │ │
|
|
934
|
+
│ │ │ localhost:8080 │ │
|
|
935
|
+
│ │ ▼ │ │
|
|
936
|
+
│ │ Firecracker VM (app running on :3000, mapped to host :8080) │ │
|
|
937
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
938
|
+
│ │
|
|
939
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
### Tunnel Configuration
|
|
943
|
+
|
|
944
|
+
```yaml
|
|
945
|
+
# /etc/cloudflared/config.yml
|
|
946
|
+
|
|
947
|
+
tunnel: gitspace-previews
|
|
948
|
+
credentials-file: /etc/cloudflared/creds.json
|
|
949
|
+
|
|
950
|
+
ingress:
|
|
951
|
+
# Wildcard for preview URLs
|
|
952
|
+
- hostname: "*.preview.gitspace.sh"
|
|
953
|
+
service: http://localhost:8080
|
|
954
|
+
|
|
955
|
+
# Catch-all
|
|
956
|
+
- service: http_status:404
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
### Per-Gitspace Tunnel Management
|
|
960
|
+
|
|
961
|
+
```typescript
|
|
962
|
+
// In gitspace-daemon or relay
|
|
963
|
+
|
|
964
|
+
async function createPreviewTunnel(gitspaceId: string, port: number) {
|
|
965
|
+
const hostname = `${gitspaceId}.preview.gitspace.sh`;
|
|
966
|
+
|
|
967
|
+
// Create DNS record pointing to tunnel
|
|
968
|
+
await cloudflare.dns.create({
|
|
969
|
+
zone: 'gitspace.sh',
|
|
970
|
+
type: 'CNAME',
|
|
971
|
+
name: hostname,
|
|
972
|
+
content: 'tunnel-id.cfargotunnel.com',
|
|
973
|
+
proxied: true,
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
// Update tunnel ingress
|
|
977
|
+
await updateTunnelConfig(hostname, `http://localhost:${port}`);
|
|
978
|
+
|
|
979
|
+
return `https://${hostname}`;
|
|
980
|
+
}
|
|
981
|
+
```
|
|
982
|
+
|
|
983
|
+
### Pricing
|
|
984
|
+
|
|
985
|
+
| Tier | Tunnels | Cost |
|
|
986
|
+
|------|---------|------|
|
|
987
|
+
| Free | 50 | $0 |
|
|
988
|
+
| Pro | Unlimited | $20/mo |
|
|
989
|
+
|
|
990
|
+
---
|
|
991
|
+
|
|
992
|
+
## GCP Integration
|
|
993
|
+
|
|
994
|
+
### Spot VMs for Cost Savings
|
|
995
|
+
|
|
996
|
+
| VM Type | On-Demand | Spot | Savings |
|
|
997
|
+
|---------|-----------|------|---------|
|
|
998
|
+
| n2-standard-2 | ~$50/mo | ~$15/mo | 70% |
|
|
999
|
+
| n2-standard-4 | ~$100/mo | ~$30/mo | 70% |
|
|
1000
|
+
| n2-standard-8 | ~$200/mo | ~$60/mo | 70% |
|
|
1001
|
+
|
|
1002
|
+
### Provisioning a Host
|
|
1003
|
+
|
|
1004
|
+
```typescript
|
|
1005
|
+
async function provisionHost(zone: string, spot: boolean) {
|
|
1006
|
+
const hostId = crypto.randomUUID();
|
|
1007
|
+
|
|
1008
|
+
await gcp.instances.insert({
|
|
1009
|
+
project: PROJECT_ID,
|
|
1010
|
+
zone,
|
|
1011
|
+
requestBody: {
|
|
1012
|
+
name: `gitspace-host-${hostId}`,
|
|
1013
|
+
machineType: `zones/${zone}/machineTypes/n2-standard-8`,
|
|
1014
|
+
|
|
1015
|
+
scheduling: {
|
|
1016
|
+
provisioningModel: spot ? 'SPOT' : 'STANDARD',
|
|
1017
|
+
instanceTerminationAction: 'DELETE',
|
|
1018
|
+
onHostMaintenance: 'TERMINATE',
|
|
1019
|
+
},
|
|
1020
|
+
|
|
1021
|
+
disks: [
|
|
1022
|
+
{
|
|
1023
|
+
boot: true,
|
|
1024
|
+
autoDelete: true,
|
|
1025
|
+
initializeParams: {
|
|
1026
|
+
sourceImage: `projects/${PROJECT_ID}/global/images/family/gitspace-host`,
|
|
1027
|
+
diskSizeGb: 100,
|
|
1028
|
+
},
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
// Attached SSD for volumes
|
|
1032
|
+
autoDelete: false,
|
|
1033
|
+
initializeParams: {
|
|
1034
|
+
diskType: `zones/${zone}/diskTypes/pd-ssd`,
|
|
1035
|
+
diskSizeGb: 500,
|
|
1036
|
+
},
|
|
1037
|
+
},
|
|
1038
|
+
],
|
|
1039
|
+
|
|
1040
|
+
networkInterfaces: [{
|
|
1041
|
+
network: 'global/networks/gitspace-vpc',
|
|
1042
|
+
accessConfigs: [{ type: 'ONE_TO_ONE_NAT' }],
|
|
1043
|
+
}],
|
|
1044
|
+
|
|
1045
|
+
metadata: {
|
|
1046
|
+
items: [
|
|
1047
|
+
{ key: 'host-id', value: hostId },
|
|
1048
|
+
{ key: 'startup-script-url', value: 'gs://gitspace-scripts/startup.sh' },
|
|
1049
|
+
],
|
|
1050
|
+
},
|
|
1051
|
+
|
|
1052
|
+
tags: { items: ['gitspace-host', 'nomad-client'] },
|
|
1053
|
+
|
|
1054
|
+
serviceAccounts: [{
|
|
1055
|
+
email: `gitspace-host@${PROJECT_ID}.iam.gserviceaccount.com`,
|
|
1056
|
+
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
|
|
1057
|
+
}],
|
|
1058
|
+
},
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
return hostId;
|
|
1062
|
+
}
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
### Auto-Scaling Logic
|
|
1066
|
+
|
|
1067
|
+
```typescript
|
|
1068
|
+
async function checkAndScale() {
|
|
1069
|
+
const capacity = await getClusterCapacity();
|
|
1070
|
+
const pending = await getPendingAllocations();
|
|
1071
|
+
|
|
1072
|
+
const utilization = capacity.cpu.used / capacity.cpu.total;
|
|
1073
|
+
|
|
1074
|
+
// Scale up: pending jobs or high utilization
|
|
1075
|
+
if (pending.length > 0 || utilization > 0.8) {
|
|
1076
|
+
if (capacity.nodes < MAX_HOSTS) {
|
|
1077
|
+
await provisionHost(pickZone(), /* spot */ true);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// Scale down: low utilization
|
|
1082
|
+
if (utilization < 0.2 && capacity.nodes > MIN_HOSTS) {
|
|
1083
|
+
const idleHost = await findIdleHost();
|
|
1084
|
+
if (idleHost && await hasBeenIdleFor(idleHost, 10 * 60 * 1000)) {
|
|
1085
|
+
await terminateHost(idleHost);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
```
|
|
1090
|
+
|
|
1091
|
+
### Handling Spot Preemption
|
|
1092
|
+
|
|
1093
|
+
```bash
|
|
1094
|
+
#!/bin/bash
|
|
1095
|
+
# /usr/local/bin/preemption-handler.sh (runs on each host)
|
|
1096
|
+
|
|
1097
|
+
while true; do
|
|
1098
|
+
PREEMPTED=$(curl -s -H "Metadata-Flavor: Google" \
|
|
1099
|
+
http://metadata.google.internal/computeMetadata/v1/instance/preempted)
|
|
1100
|
+
|
|
1101
|
+
if [ "$PREEMPTED" = "TRUE" ]; then
|
|
1102
|
+
echo "Preemption notice, draining..."
|
|
1103
|
+
|
|
1104
|
+
# Drain Nomad node
|
|
1105
|
+
nomad node drain -self -enable -deadline 25s -force
|
|
1106
|
+
|
|
1107
|
+
# Stop Flintlock VMs gracefully
|
|
1108
|
+
flintlock-ctl microvm list | xargs -I {} flintlock-ctl microvm delete {}
|
|
1109
|
+
|
|
1110
|
+
exit 0
|
|
1111
|
+
fi
|
|
1112
|
+
|
|
1113
|
+
sleep 5
|
|
1114
|
+
done
|
|
1115
|
+
```
|
|
1116
|
+
|
|
1117
|
+
---
|
|
1118
|
+
|
|
1119
|
+
## Local Development: Mac Parity
|
|
1120
|
+
|
|
1121
|
+
### Requirements
|
|
1122
|
+
|
|
1123
|
+
- macOS 15 (Sequoia) or later
|
|
1124
|
+
- Apple Silicon M2 or M3 (with hardware nested virtualization)
|
|
1125
|
+
- Lima v1.0+
|
|
1126
|
+
|
|
1127
|
+
### Architecture
|
|
1128
|
+
|
|
1129
|
+
```
|
|
1130
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
1131
|
+
│ macOS Host │
|
|
1132
|
+
│ │
|
|
1133
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
1134
|
+
│ │ Lima VM (ARM64 Linux) │ │
|
|
1135
|
+
│ │ nestedVirtualization: true │ │
|
|
1136
|
+
│ │ │ │
|
|
1137
|
+
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
|
|
1138
|
+
│ │ │ KVM │ │ │
|
|
1139
|
+
│ │ └──────────────────────────┬──────────────────────────────────┘ │ │
|
|
1140
|
+
│ │ │ │ │
|
|
1141
|
+
│ │ ┌──────────────────────────▼──────────────────────────────────┐ │ │
|
|
1142
|
+
│ │ │ Firecracker (aarch64) │ │ │
|
|
1143
|
+
│ │ │ │ │ │
|
|
1144
|
+
│ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │
|
|
1145
|
+
│ │ │ │ MicroVM │ │ MicroVM │ │ MicroVM │ │ │ │
|
|
1146
|
+
│ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ │
|
|
1147
|
+
│ │ └──────────────────────────────────────────────────────────────┘ │ │
|
|
1148
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
1149
|
+
│ │
|
|
1150
|
+
└──────────────────────────────────────────────────────────────────────────────┘
|
|
1151
|
+
```
|
|
1152
|
+
|
|
1153
|
+
### Lima Setup
|
|
1154
|
+
|
|
1155
|
+
```bash
|
|
1156
|
+
# Create Lima instance with nested virtualization
|
|
1157
|
+
limactl create \
|
|
1158
|
+
--name=gitspace \
|
|
1159
|
+
--vm-type=vz \
|
|
1160
|
+
--mount-type=virtiofs \
|
|
1161
|
+
--set '.nestedVirtualization = true' \
|
|
1162
|
+
template://ubuntu
|
|
1163
|
+
|
|
1164
|
+
# Start it
|
|
1165
|
+
limactl start gitspace
|
|
1166
|
+
|
|
1167
|
+
# Shell in
|
|
1168
|
+
limactl shell gitspace
|
|
1169
|
+
|
|
1170
|
+
# Inside Lima: Install Firecracker (aarch64)
|
|
1171
|
+
curl -L https://github.com/firecracker-microvm/firecracker/releases/download/v1.5.0/firecracker-v1.5.0-aarch64.tgz | tar -xz
|
|
1172
|
+
sudo mv release-*/firecracker /usr/local/bin/
|
|
1173
|
+
|
|
1174
|
+
# Verify KVM works
|
|
1175
|
+
ls -la /dev/kvm
|
|
1176
|
+
```
|
|
1177
|
+
|
|
1178
|
+
### Lima Configuration File
|
|
1179
|
+
|
|
1180
|
+
```yaml
|
|
1181
|
+
# ~/.lima/gitspace/lima.yaml
|
|
1182
|
+
|
|
1183
|
+
vmType: vz
|
|
1184
|
+
arch: aarch64
|
|
1185
|
+
cpus: 4
|
|
1186
|
+
memory: 8GiB
|
|
1187
|
+
disk: 100GiB
|
|
1188
|
+
|
|
1189
|
+
nestedVirtualization: true
|
|
1190
|
+
|
|
1191
|
+
mounts:
|
|
1192
|
+
- location: "~"
|
|
1193
|
+
writable: true
|
|
1194
|
+
- location: "/tmp/lima"
|
|
1195
|
+
writable: true
|
|
1196
|
+
|
|
1197
|
+
provision:
|
|
1198
|
+
- mode: system
|
|
1199
|
+
script: |
|
|
1200
|
+
#!/bin/bash
|
|
1201
|
+
apt-get update
|
|
1202
|
+
apt-get install -y containerd
|
|
1203
|
+
|
|
1204
|
+
# Install Firecracker
|
|
1205
|
+
curl -L https://github.com/firecracker-microvm/firecracker/releases/download/v1.5.0/firecracker-v1.5.0-aarch64.tgz | tar -xz -C /usr/local/bin
|
|
1206
|
+
|
|
1207
|
+
# Install Flintlock
|
|
1208
|
+
curl -L https://github.com/liquidmetal-dev/flintlock/releases/download/v0.9.0/flintlock_0.9.0_linux_arm64.tar.gz | tar -xz -C /usr/local/bin
|
|
1209
|
+
```
|
|
1210
|
+
|
|
1211
|
+
### Parity Matrix
|
|
1212
|
+
|
|
1213
|
+
| Component | Mac (Lima) | GCP (Spot VM) |
|
|
1214
|
+
|-----------|------------|---------------|
|
|
1215
|
+
| Outer VM | Lima (Virtualization.framework) | GCE (KVM) |
|
|
1216
|
+
| Inner VM | Firecracker (aarch64) | Firecracker (x86_64) |
|
|
1217
|
+
| Image format | Same OCI image | Same OCI image |
|
|
1218
|
+
| Storage | Local SSD | PD-SSD + btrfs |
|
|
1219
|
+
| Networking | Lima bridge | GCP VPC + NAT |
|
|
1220
|
+
|
|
1221
|
+
**Key insight:** Same OCI images, same Firecracker, same gitspace experience. Only the architecture (arm64 vs x86_64) and outer layer differ.
|
|
1222
|
+
|
|
1223
|
+
---
|
|
1224
|
+
|
|
1225
|
+
## Components to Build
|
|
1226
|
+
|
|
1227
|
+
### Option A: DIY Orchestration
|
|
1228
|
+
|
|
1229
|
+
| Component | Description | Effort |
|
|
1230
|
+
|-----------|-------------|--------|
|
|
1231
|
+
| **gitspace-relay** | WebSocket relay, auth, GitHub hooks | High |
|
|
1232
|
+
| **gitspace-daemon** | Runs on each host, manages VMs | High |
|
|
1233
|
+
| **Custom scheduler** | Track hosts, schedule gitspaces | Medium |
|
|
1234
|
+
| **Volume manager** | btrfs subvolumes, quotas | Medium |
|
|
1235
|
+
| **Tunnel manager** | Cloudflare tunnel per gitspace | Low |
|
|
1236
|
+
| **Image builder** | BuildKit pipeline | Medium |
|
|
1237
|
+
| **CLI** | User-facing commands | Medium |
|
|
1238
|
+
| **Web UI** | Dashboard (optional) | High |
|
|
1239
|
+
|
|
1240
|
+
**Total: 6-8 significant components**
|
|
1241
|
+
|
|
1242
|
+
### Option B: Flintlock + Nomad
|
|
1243
|
+
|
|
1244
|
+
| Component | Description | Effort |
|
|
1245
|
+
|-----------|-------------|--------|
|
|
1246
|
+
| **gitspace-relay** | WebSocket relay, auth, GitHub hooks, Nomad job submission | High |
|
|
1247
|
+
| **Nomad job templates** | HCL templates for gitspaces | Low |
|
|
1248
|
+
| **Volume manager** | btrfs subvolumes, quotas | Medium |
|
|
1249
|
+
| **Tunnel manager** | Cloudflare tunnel per gitspace | Low |
|
|
1250
|
+
| **Image builder** | BuildKit pipeline (OCI for Flintlock) | Medium |
|
|
1251
|
+
| **Host provisioner** | GCP API for scale up/down | Medium |
|
|
1252
|
+
| **CLI** | User-facing commands | Medium |
|
|
1253
|
+
|
|
1254
|
+
**Uses:** Nomad (scheduling), Flintlock (VM lifecycle), containerd (images)
|
|
1255
|
+
|
|
1256
|
+
**Total: 5-6 components, leveraging battle-tested tools**
|
|
1257
|
+
|
|
1258
|
+
### Option C: Nomad Only
|
|
1259
|
+
|
|
1260
|
+
| Component | Description | Effort |
|
|
1261
|
+
|-----------|-------------|--------|
|
|
1262
|
+
| **gitspace-relay** | WebSocket relay, auth, Nomad job submission | High |
|
|
1263
|
+
| **gitspace-daemon** | Lighter version, manages FC directly | Medium |
|
|
1264
|
+
| **Nomad job templates** | HCL with raw_exec for Firecracker | Medium |
|
|
1265
|
+
| **Volume manager** | btrfs subvolumes, quotas | Medium |
|
|
1266
|
+
| **Tunnel manager** | Cloudflare tunnel per gitspace | Low |
|
|
1267
|
+
| **Image builder** | BuildKit + manual rootfs conversion | Medium |
|
|
1268
|
+
| **Host provisioner** | GCP API for scale up/down | Medium |
|
|
1269
|
+
| **CLI** | User-facing commands | Medium |
|
|
1270
|
+
|
|
1271
|
+
**Total: 6-7 components**
|
|
1272
|
+
|
|
1273
|
+
---
|
|
1274
|
+
|
|
1275
|
+
## Decision Matrix
|
|
1276
|
+
|
|
1277
|
+
| Factor | DIY | Flintlock + Nomad | Nomad Only |
|
|
1278
|
+
|--------|-----|-------------------|------------|
|
|
1279
|
+
| **Complexity** | High | Medium | Medium |
|
|
1280
|
+
| **Dependencies** | Few | Nomad, Flintlock, containerd | Nomad |
|
|
1281
|
+
| **Scheduling** | Build it | Nomad (proven) | Nomad (proven) |
|
|
1282
|
+
| **VM lifecycle** | Build it | Flintlock (clean API) | Build it |
|
|
1283
|
+
| **Image management** | Manual | containerd (OCI native) | Manual |
|
|
1284
|
+
| **Scale to zero** | Build it | Build it | Build it |
|
|
1285
|
+
| **Spot handling** | Build it | Nomad drain + build | Nomad drain + build |
|
|
1286
|
+
| **Learning curve** | Low | Medium | Low-Medium |
|
|
1287
|
+
| **Control** | Full | High | High |
|
|
1288
|
+
| **Fun** | Most | Some | Some |
|
|
1289
|
+
|
|
1290
|
+
### Recommendation
|
|
1291
|
+
|
|
1292
|
+
**Start with:** Flintlock + Nomad
|
|
1293
|
+
|
|
1294
|
+
**Why:**
|
|
1295
|
+
1. Nomad's scheduling is non-trivial to build correctly
|
|
1296
|
+
2. Flintlock's OCI integration saves significant work
|
|
1297
|
+
3. Both are well-documented with active communities
|
|
1298
|
+
4. Can always replace components later
|
|
1299
|
+
5. Focus engineering effort on the unique parts (relay, CLI, user experience)
|
|
1300
|
+
|
|
1301
|
+
**Progression:**
|
|
1302
|
+
1. Single host + Flintlock (no Nomad) for initial development
|
|
1303
|
+
2. Add Nomad when multi-host is needed
|
|
1304
|
+
3. GCP Spot integration for cost optimization
|
|
1305
|
+
4. Scale based on demand
|
|
1306
|
+
|
|
1307
|
+
---
|
|
1308
|
+
|
|
1309
|
+
## Summary
|
|
1310
|
+
|
|
1311
|
+
```
|
|
1312
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
1313
|
+
│ GITSPACE INFRASTRUCTURE │
|
|
1314
|
+
│ │
|
|
1315
|
+
│ Control Plane │
|
|
1316
|
+
│ ├── gitspace.sh relay (auth, WebSocket, GitHub, scheduling) │
|
|
1317
|
+
│ └── Nomad Server (job scheduling, cluster state) │
|
|
1318
|
+
│ │
|
|
1319
|
+
│ Data Plane (per host) │
|
|
1320
|
+
│ ├── Nomad Client (receives jobs) │
|
|
1321
|
+
│ ├── Flintlock (manages Firecracker VMs) │
|
|
1322
|
+
│ ├── containerd (OCI images) │
|
|
1323
|
+
│ ├── cloudflared (preview tunnels) │
|
|
1324
|
+
│ └── Firecracker (microVMs) │
|
|
1325
|
+
│ │
|
|
1326
|
+
│ Storage │
|
|
1327
|
+
│ ├── OCI images from registry (kernel, rootfs) │
|
|
1328
|
+
│ └── btrfs volumes (persistent workspaces) │
|
|
1329
|
+
│ │
|
|
1330
|
+
│ Networking │
|
|
1331
|
+
│ ├── Terminal: WebSocket via relay (E2E encrypted) │
|
|
1332
|
+
│ ├── Preview: Cloudflare Tunnel (HTTPS) │
|
|
1333
|
+
│ └── Outbound: NAT via host │
|
|
1334
|
+
│ │
|
|
1335
|
+
│ Hosts │
|
|
1336
|
+
│ ├── GCP Spot VMs (scale 0 to N, 70% cheaper) │
|
|
1337
|
+
│ ├── GCP On-Demand (reliable fallback) │
|
|
1338
|
+
│ ├── Latitude.sh Bare Metal (dedicated, hourly) │
|
|
1339
|
+
│ ├── Self-hosted (your computer) │
|
|
1340
|
+
│ └── Mac + Lima (local development) │
|
|
1341
|
+
│ │
|
|
1342
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
1343
|
+
```
|
|
1344
|
+
|
|
1345
|
+
---
|
|
1346
|
+
|
|
1347
|
+
*Last updated: 2024-12*
|