Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
6a16c9e
feat: ed25519 signed token package for short-lived edge proxy access
meAmitPatil Apr 8, 2026
5e6adb9
feat: terminal-token endpoint mints short-lived signed tokens for ws …
meAmitPatil Apr 8, 2026
d6222cd
feat: edge proxy token verifier, nonce dedupe, and terminal host parser
meAmitPatil Apr 8, 2026
ab3c094
feat: websocket bridge from edge proxy to boxd pty
meAmitPatil Apr 8, 2026
ef8f52a
docs: add terminal-token endpoint to openapi spec
meAmitPatil Apr 8, 2026
7aa81f3
test: integration tests for terminal ws bridge with fake boxd
meAmitPatil Apr 8, 2026
080a473
chore: terminal keypair distribution for edge proxy deploys
meAmitPatil Apr 8, 2026
effe083
fix: address review feedback for web terminal
meAmitPatil Apr 8, 2026
ddb7791
ci: plumb terminal_allowed_origins and require_terminal vars to deplo…
meAmitPatil Apr 8, 2026
023584b
fix: switch to ssl proxy lb and add http-to-https redirect listener
meAmitPatil Apr 8, 2026
84007e5
proxy: extract shared sandbox authz, add ScopeFiles and /files bridge…
meAmitPatil Apr 8, 2026
2d6c639
api: add /sandboxes/:id/file-token mint, share preamble with terminal…
meAmitPatil Apr 8, 2026
6fffc3c
remove legacy file upload/download via controlplane+VMD; edge proxy i…
meAmitPatil Apr 8, 2026
95d719e
tests: cover /sandboxes/:id/file-token and edge-proxy /files bridge; …
meAmitPatil Apr 8, 2026
7ca4234
docs: openapi for /file-token, remove legacy /files/{path} routes
meAmitPatil Apr 8, 2026
c891098
proxy: drop stray comment reference
meAmitPatil Apr 8, 2026
08e2fa2
proxy: unify terminal + files under reserved 'boxd-' host label
meAmitPatil Apr 8, 2026
876ebd3
proxy: delegate /health on sandbox hosts to boxd-label lockdown
meAmitPatil Apr 8, 2026
01260c4
proxy: reject '..' segments in /files ?path= at the edge
meAmitPatil Apr 8, 2026
8cc2b79
boxd: remove artificial 512MB upload cap, return clean 507 on ENOSPC
meAmitPatil Apr 9, 2026
ebb1147
auth: replace Ed25519 signed tokens with per-sandbox HMAC access tokens
meAmitPatil Apr 9, 2026
9457bfa
api: return access_token on create, resume, and get sandbox responses
meAmitPatil Apr 10, 2026
8a2d980
proxy: remove ?token= query carrier, fix stale comments from code review
meAmitPatil Apr 10, 2026
c42a73b
proxy: fix files-only deploy, LRU cache eviction, graceful redirect s…
meAmitPatil Apr 10, 2026
33b4956
api: remove legacy /instances endpoints and ip_address from all respo…
meAmitPatil Apr 10, 2026
383ba20
api: VMD reports actual resources in CreateVMResponse, single Activat…
meAmitPatil Apr 10, 2026
336779e
api: use placeholder 1 instead of 0 for initial sandbox resources (DB…
meAmitPatil Apr 10, 2026
9182ad9
test: update integration test to expect actual VM memory (1024 MiB)
meAmitPatil Apr 10, 2026
3a002a7
api: ResumeInstance returns actual resources; proxy: enforce connecti…
meAmitPatil Apr 10, 2026
7eb5e99
feat: support environment variables at sandbox creation
meAmitPatil Apr 10, 2026
87a6d75
api: validate env_vars size limits
meAmitPatil Apr 10, 2026
e8cb697
phase 0: add host table, make sandbox.host_id NOT NULL
meAmitPatil Apr 10, 2026
99b9418
phase 1: add HostRegistry + Scheduler, route all VMD calls through ho…
meAmitPatil Apr 10, 2026
1e6e0e9
extract VMDClient to vmdclient package, fix type safety and legacy ho…
meAmitPatil Apr 10, 2026
27a9d90
phase 2: add systemd units, BoltDB state store, and systemctl wrapper
meAmitPatil Apr 10, 2026
c02ff81
phase 2: wire systemd mode, BoltDB persistence, and startup reattach …
meAmitPatil Apr 10, 2026
2f4aae5
phase 2: fix PauseVM, ResumeVM, RestoreVMSnapshot for systemd mode; i…
meAmitPatil Apr 10, 2026
d5263fb
phase 3: handler degradation for VMD NotFound (410 Gone, stateless re…
meAmitPatil Apr 11, 2026
5a61ddc
phase 3: continuous reconciler for BoltDB ↔ systemd drift with rate-l…
meAmitPatil Apr 11, 2026
150105d
phase 3: three-way reconciler (BoltDB + systemd + DB) with audit log
meAmitPatil Apr 11, 2026
edd0ad2
phase 3 review fixes: tenant-scoped GetSnapshot, reconciler ctx+timeo…
meAmitPatil Apr 11, 2026
d04cd45
perf(create): cache scheduler host list + parallelize DB INSERT with …
meAmitPatil Apr 11, 2026
d3ed822
perf(create): parallelize rootfs/netns setup in VMD and activate/netw…
meAmitPatil Apr 11, 2026
47d0cbc
fix(vm): protect VMInstance field writes with inst.mu in CreateVM/col…
meAmitPatil Apr 11, 2026
00c6d1d
perf(vmd): skip MainPID systemctl lookup on create hot path (~15ms)
meAmitPatil Apr 11, 2026
ba7c130
perf(pause): collapse 5 DB roundtrips to 2 via BeginPause + FinalizeP…
meAmitPatil Apr 11, 2026
7df24bc
pause review: gate FinalizePause INSERT on target row, use SandboxExi…
meAmitPatil Apr 11, 2026
a1a0413
phase 4: VMD heartbeat, LeastLoaded scheduler, unhealthy host detection
meAmitPatil Apr 12, 2026
c4f1fcc
phase 4 review: auth on internal endpoints, auto-recovery from unheal…
meAmitPatil Apr 12, 2026
6503fa1
split deploy-vmd and deploy-proxy pipelines, install systemd units in…
meAmitPatil Apr 12, 2026
8ccf213
auto-seed default host row at startup from env vars
meAmitPatil Apr 12, 2026
ba5bd14
scheduler falls back to DefaultHostID when host table is empty, remov…
meAmitPatil Apr 12, 2026
a35a2df
remove systemd feature flag, delete old child-process paths, cache te…
meAmitPatil Apr 12, 2026
c360e8f
scheduler: power-of-two-choices for multi-host, network: pre-allocate…
meAmitPatil Apr 13, 2026
72a0968
scheduler: power-of-two-choices, network: pre-allocated slot pool wit…
meAmitPatil Apr 13, 2026
a0ea991
fix(net pool): skip occupied slots instead of infinite retry on stale…
meAmitPatil Apr 14, 2026
4f22ceb
fix: remove all hardcoded paths, derive from config/env vars
meAmitPatil Apr 14, 2026
c81abe0
fix: standardize all config paths to /etc/sandbox/
meAmitPatil Apr 14, 2026
86aa638
fix: use /bin/sh -c for ExecStart so RUN_DIR env var expands
meAmitPatil Apr 14, 2026
fc8389d
fix: hardcode /var/lib/sandbox/rundir in firecracker unit
meAmitPatil Apr 14, 2026
60910ff
fix: use vmID as rundir name so systemd %i matches the data directory
meAmitPatil Apr 14, 2026
b3b2daa
fix: don't delete template files on shutdown so hash caching works ac…
meAmitPatil Apr 14, 2026
f84a158
fix(deploy): skip boxd rootfs inject when binary unchanged, preserves…
meAmitPatil Apr 14, 2026
a2a7ec4
fix(deploy): skip boxd build and rootfs inject when boxd source uncha…
meAmitPatil Apr 14, 2026
8dfa8e2
fix: detect dead VMs via connection errors, not just gRPC NotFound (4…
meAmitPatil Apr 14, 2026
9a9316c
fix: VMD detects dead VMs on connection error, cleans up and returns …
meAmitPatil Apr 14, 2026
ea422e5
fix: remove snapshotFileExists check — control plane can't see VMD's …
meAmitPatil Apr 14, 2026
7ef6ddf
review fixes: lock ordering, TOCTOU race, template meta, async PID, p…
meAmitPatil Apr 14, 2026
49c7535
remove half-implemented auto-resume, rename idle to paused, add orpha…
meAmitPatil Apr 14, 2026
953b166
wire OpenTelemetry across all binaries with no-op default and per-hos…
meAmitPatil Apr 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 172 additions & 0 deletions .github/workflows/deploy-proxy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
name: Deploy Proxy

on:
workflow_dispatch:
push:
branches: [main]
paths:
- 'cmd/proxy/**'
- 'internal/proxy/**'
- 'deploy/proxy.service'

jobs:
deploy:
runs-on: ubuntu-latest
environment: staging
permissions:
contents: read
id-token: write

steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true

- name: Build proxy binary
run: |
mkdir -p bin
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags '-s -w' -o bin/proxy ./cmd/proxy

- name: Authenticate to GCP
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}

- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v2

- name: Discover and deploy proxy to VMD instances
env:
GCP_PROJECT: ${{ vars.GCP_PROJECT }}
VMD_LABEL: ${{ vars.VMD_LABEL }}
VMD_INSTALL_DIR: ${{ vars.VMD_INSTALL_DIR }}
SHA: ${{ github.sha }}
SANDBOX_ACCESS_TOKEN_SEED: ${{ secrets.SANDBOX_ACCESS_TOKEN_SEED_STAGING }}
TERMINAL_ALLOWED_ORIGINS: ${{ vars.TERMINAL_ALLOWED_ORIGINS_STAGING }}
REQUIRE_DATA_PLANE: ${{ vars.REQUIRE_DATA_PLANE_STAGING }}
run: |
python3 - <<'PYEOF'
import os, sys, subprocess, textwrap
from concurrent.futures import ThreadPoolExecutor, as_completed
import re as _re

project = os.environ['GCP_PROJECT']
label = os.environ.get('VMD_LABEL', 'component=vmd')
install_dir = os.environ.get('VMD_INSTALL_DIR', '/usr/local/bin')
sha = os.environ['SHA'][:8]

access_seed = os.environ.get('SANDBOX_ACCESS_TOKEN_SEED', '')
if access_seed and not _re.fullmatch(r'[0-9a-fA-F]{64,}', access_seed):
print('ERROR: SANDBOX_ACCESS_TOKEN_SEED must be hex-encoded, >= 32 bytes (64 hex chars)', file=sys.stderr)
sys.exit(1)
terminal_origins = os.environ.get('TERMINAL_ALLOWED_ORIGINS', '')
if terminal_origins and not _re.fullmatch(r'[A-Za-z0-9.,:/*\-]+', terminal_origins):
print('ERROR: TERMINAL_ALLOWED_ORIGINS contains disallowed characters', file=sys.stderr)
sys.exit(1)
require_data_plane = os.environ.get('REQUIRE_DATA_PLANE', '')
if require_data_plane not in ('', '0', '1'):
print('ERROR: REQUIRE_DATA_PLANE must be empty, "0", or "1"', file=sys.stderr)
sys.exit(1)

result = subprocess.run([
'gcloud', 'compute', 'instances', 'list',
f'--project={project}',
f'--filter=labels.{label} AND status=RUNNING',
'--format=csv[no-heading](name,zone)',
], capture_output=True, text=True, check=True)

instances = [
{'name': r[0], 'zone': r[1]}
for line in result.stdout.strip().splitlines()
if line.strip()
for r in [line.strip().split(',')]
]

if not instances:
print(f'No instances with label {label} found in {project}', file=sys.stderr)
sys.exit(1)

print(f'Deploying proxy to {len(instances)} instance(s)')

def deploy(inst):
name, zone = inst['name'], inst['zone']
tag = f'{name}/{zone}'

for src, dst in [
('bin/proxy', f'/tmp/proxy-{sha}'),
('deploy/proxy.service', '/tmp/proxy.service'),
]:
subprocess.run([
'gcloud', 'compute', 'scp', src, f'{name}:{dst}',
f'--zone={zone}', f'--project={project}',
'--quiet', '--tunnel-through-iap',
], check=True, capture_output=True)
print(f'[{tag}] proxy uploaded')

# Deploy proxy only. VMD is NOT restarted.
deploy_script = textwrap.dedent(f'''
set -euo pipefail

sudo mv /tmp/proxy-{sha} {install_dir}/proxy
sudo chmod +x {install_dir}/proxy

sudo mv /tmp/proxy.service /etc/systemd/system/proxy.service
sudo systemctl daemon-reload
sudo systemctl enable proxy

sudo mkdir -p /etc/sandbox
if [ -n "{access_seed}" ]; then
sudo tee /etc/sandbox/proxy.env > /dev/null <<PROXYENV
SANDBOX_ACCESS_TOKEN_SEED={access_seed}
TERMINAL_ALLOWED_ORIGINS={terminal_origins}
REQUIRE_DATA_PLANE={require_data_plane}
PROXYENV
sudo chmod 0600 /etc/sandbox/proxy.env
fi

sudo systemctl restart proxy
sleep 3
sudo systemctl is-active --quiet proxy || (
echo "ERROR: proxy failed to become active after restart" >&2
sudo systemctl status --no-pager proxy >&2 || true
sudo journalctl -u proxy --no-pager -n 40 >&2 || true
exit 1
)
''')
r = subprocess.run([
'gcloud', 'compute', 'ssh', name,
f'--zone={zone}', f'--project={project}',
'--quiet', '--tunnel-through-iap',
'--command', deploy_script,
], capture_output=True, text=True)
if r.returncode != 0:
raise RuntimeError(
f'proxy not healthy\n'
f'--- stdout ---\n{r.stdout}\n'
f'--- stderr ---\n{r.stderr}'
)
print(f'[{tag}] proxy active')

failed = []
with ThreadPoolExecutor(max_workers=len(instances)) as ex:
futures = {ex.submit(deploy, inst): inst for inst in instances}
for f in as_completed(futures):
inst = futures[f]
try:
f.result()
except Exception as e:
tag = f"{inst['name']}/{inst['zone']}"
print(f'[{tag}] FAILED: {e}', file=sys.stderr)
failed.append(tag)

if failed:
print(f'Deploy failed on: {", ".join(failed)}', file=sys.stderr)
sys.exit(1)

print(f'Deployed proxy to {len(instances)} instance(s). sha={sha}')
PYEOF
Loading