Skip to content

refactor: replace Rust server with apps workspace#460

Merged
DorianZheng merged 2 commits into
mainfrom
replace-server-apps-workspace
May 2, 2026
Merged

refactor: replace Rust server with apps workspace#460
DorianZheng merged 2 commits into
mainfrom
replace-server-apps-workspace

Conversation

@DorianZheng

Copy link
Copy Markdown
Member

Summary

  • add the apps workspace with API, dashboard, runner, daemon, proxy, CLI, and generated clients
  • remove the old Rust boxlite-server crate from the Cargo workspace and Make targets
  • adapt the runner to the current Go SDK execution and structured registry APIs
  • include all Go app modules in the root go.work

Tests

  • git diff --cached --check
  • cargo metadata --locked --no-deps
  • cargo fmt --all -- --check
  • BOXLITE_DEPS_STUB=1 cargo check --workspace --all-targets --all-features --exclude boxlite-guest
  • for dir in apps/api-client-go apps/cli apps/common-go apps/daemon apps/libs/computer-use apps/otel-collector/exporter apps/proxy apps/runner apps/snapshot-manager apps/ssh-gateway; do (cd "$dir" && go test -tags boxlite_dev ./...); done

Comment on lines +251 to +257
const runnerResponse = await fetch(targetUrl, {
method: req.method,
headers: {
Authorization: `Bearer ${runner.apiKey}`,
Accept: req.header('accept') || 'text/event-stream',
},
})
}

export function generateApiKeyHash(value: string): string {
return crypto.createHash('sha256').update(value).digest('hex')
* Empty URLs are assumed to be Docker Hub.
*/
function normalizeRegistryUrl(url: string): string {
if (!url || url.trim() === '' || url.toLowerCase().includes('docker.io')) {
return DOCKER_HUB_URL
}
// Strip trailing slashes for consistent matching
return url.trim().replace(/\/+$/, '')

// Add default Docker Hub registry only if user doesn't have their own Docker Hub credentials
// The auth configs map is keyed by URL, so adding the default last would override user credentials
const userHasDockerHubCreds = sourceRegistries.some((registry) => registry.url.includes('docker.io'))
Comment on lines +279 to +286
await axios.delete(
`${this.configService.getOrThrow('oidc.issuer')}/api/v2/users/${authContext.userId}/identities/${provider}/${providerUserId}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
},
)
return [
`\n\n${ind}// Run code securely inside the Sandbox`,
`${ind}const codeRunResponse = await sandbox.process.codeRun(\``,
`${(p.state['codeRunParams'].languageCode ?? '').replace(/`/g, '\\`').replace(/\$\{/g, '\\${')}`, // Escape backticks and ${ to prevent breaking the template literal
}

function escapePromiseSpecialCharacters(contents) {
return contents.replace(/`Promise`\s*\\<((?:`?[^`<>]+`?|<[^<>]+>)*?)>/g, (_match, typeContent) => {
type = indexSignatureType
}

type = type.replace(/([*_`\[\]()<>|])/g, '\\$1')
runCommands(...commands: (string | string[])[]): Image {
for (const command of commands) {
if (Array.isArray(command)) {
this._dockerfile += `RUN ${command.map((c) => `"${c.replace(/"/g, '\\\\\\"').replace(/'/g, "\\'")}"`).join(' ')}\n`

// Calculate how many bytes we can safely process
// We need to keep bytes that could potentially be the start of a prefix marker
let safeLen = buf.length
<TooltipTrigger asChild>
{labelCount > 0 ? (
<div className="truncate w-fit bg-blue-100 rounded-sm text-blue-800 dark:bg-blue-950 dark:text-blue-200 px-1">
{labelCount > 0 ? (labelCount === 1 ? '1 label' : `${labelCount} labels`) : '/'}
Updating...
</Button>
) : (
<Button type="submit" form="update-region-form" variant="default" disabled={loading || !hasChanges}>
className={cn({
'text-foreground': checked,
'text-muted-foreground': !checked,
'hover:underline': !checked && link,
})
}}
aria-label="Select all"
disabled={!deletePermitted || loading}
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
disabled={!deletePermitted || loadingSnapshots[row.original.id] || loading}
if (!isOpen) setRegionToUpdate(null)
}}
onUpdateRegion={handleUpdateRegion}
loading={regionToUpdate ? regionIsLoading[regionToUpdate.id] || false : false}
return cleanedContent
}

return modified ? lines.join('\n') : contents
const countText = count === 1 ? 'this volume' : `these ${count} selected volumes`

return (
<AlertDialog open={action !== null} onOpenChange={(open) => !open && onCancel()}>

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

if err != nil {
return "", fmt.Errorf("error opening profile file (%s): %s", profilePath, err)
}
defer profile.Close()
if err != nil {
logger.Error("Failed to open log file", "path", c.DaemonLogFilePath, "error", err)
} else {
defer logFile.Close()
if err != nil {
return common_errors.NewInternalServerError(fmt.Errorf("failed to open input pipe: %w", err))
}
defer f.Close()
if err != nil {
s.logger.Debug("failed to open log file to echo input", "error", err)
} else {
defer logFile.Close()
if err != nil {
return err
}
defer out.Close()
log.Errorf("Failed to open log file for %s: %v", process.Name, err)
} else {
process.cmd.Stdout = logFile
defer logFile.Close()
log.Errorf("Failed to open error file for %s: %v", process.Name, err)
} else {
process.cmd.Stderr = errFile
defer errFile.Close()
if err != nil {
return fmt.Errorf("failed to create host key file: %w", err)
}
defer privateKeyFile.Close()
@DorianZheng DorianZheng force-pushed the replace-server-apps-workspace branch from f24885d to 82a375b Compare May 2, 2026 05:20
@DorianZheng DorianZheng force-pushed the replace-server-apps-workspace branch from 82a375b to fba3bb9 Compare May 2, 2026 09:54
@DorianZheng DorianZheng merged commit a1d7211 into main May 2, 2026
29 of 30 checks passed
@DorianZheng DorianZheng deleted the replace-server-apps-workspace branch May 2, 2026 10:15
lilongen pushed a commit to lilongen/boxlite that referenced this pull request Jun 8, 2026
apps/api/src/user/user.service.ts has imported `node-forge` (SSH key PEM →
OpenSSH conversion) since boxlite-ai#460 (the Rust-server → apps-workspace migration),
but the package was never a declared dependency on main. An earlier branch
commit (a946f7c) declared it, then d86eb14 dropped it again as an "unrelated
dep change" on the assumption that main resolves node-forge transitively.

That assumption no longer holds: nothing in the resolved tree depends on
node-forge (yarn why shows no transitive path), so webpack fails to resolve
the import and `nx serve api` won't build locally. Root cause is the same as
the typeorm drift — apps/yarn.lock is gitignored, so a floating range that
once hoisted node-forge no longer does. A directly-imported package must be a
direct dependency, not rely on transitive hoisting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lilongen pushed a commit to lilongen/boxlite that referenced this pull request Jun 9, 2026
apps/api/src/user/user.service.ts has imported `node-forge` (SSH key PEM →
OpenSSH conversion) since boxlite-ai#460 (the Rust-server → apps-workspace migration),
but the package was never a declared dependency on main. An earlier branch
commit (a946f7c) declared it, then d86eb14 dropped it again as an "unrelated
dep change" on the assumption that main resolves node-forge transitively.

That assumption no longer holds: nothing in the resolved tree depends on
node-forge (yarn why shows no transitive path), so webpack fails to resolve
the import and `nx serve api` won't build locally. Root cause is the same as
the typeorm drift — apps/yarn.lock is gitignored, so a floating range that
once hoisted node-forge no longer does. A directly-imported package must be a
direct dependency, not rely on transitive hoisting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
G4614 added a commit to G4614/boxlite that referenced this pull request Jun 10, 2026
The SST production webpack build (apps/api/Dockerfile → nx build api
--configuration=production) has been silently broken on main. No CI step
runs this path, and e2e uses ts-node --transpile-only, so two structural
issues went unnoticed since boxlite-ai#460:

1. webpack.config.js registers each migration file as a webpack entry
   using the cwd-relative path returned by glob.sync (e.g. "apps/api/src/
   migrations/<X>-migration.ts"). Webpack resolves entry values against
   config.context, which @nx/webpack sets to the project root
   ("/boxlite/apps/api"), producing "/boxlite/apps/api/apps/api/src/...".
   Result: 91 "Module not found" errors. Fix: map each match through
   path.resolve(process.cwd(), ...) so entries are absolute.

2. The Dockerfile copies apps/tsconfig.base.json to /boxlite/ but
   apps/api/tsconfig.json extends "../tsconfig.base.json", resolving to
   /boxlite/apps/tsconfig.base.json. Result: TS5083 missing-tsconfig.
   Fix: split the COPY so tsconfig.base.json lands at apps/tsconfig.base.json.

These two fixes are the bare minimum to get the prod webpack build past
its structural blockers. The build now reaches tsc and surfaces a
pre-existing class of 163 type errors (Express 5 ParamsDictionary
widening + an OTel dep duplication + a libs/ link gap) — those are a
separate concern and will be addressed in follow-up PRs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DorianZheng added a commit that referenced this pull request Jun 10, 2026
…wagger spec (#721)

## Summary

Closes out the **analytics-api-client** item of #711 (Sandbox→Box Part 1
follow-ups).

#706 renamed the dashboard's analytics consumers to Box-named symbols
(`ModelsBoxUsage`, `organizationOrganizationIdUsageBoxGet`, …) but the
committed client still only exported Sandbox-named ones — the dashboard
could not typecheck against the in-repo client. The client also couldn't
be regenerated from this repo at all: its generator input was an
uncommitted `tmp/swagger.json` from the external analytics service (the
reason #716 excluded it).

## Changes

- **Commit the contract in-repo**:
`apps/libs/analytics-api-client/swagger.json`, reconstructed 1:1 from
the previously committed client with only sandbox→box renamed (paths
`/organization/{organizationId}/box/{boxId}/…`, `models.BoxUsage`,
`boxId`, `boxCount`). This is now the spec any future analytics service
must implement — same spec-first direction as
`openapi/box.openapi.yaml`.
- **Point `generate:api-client` at it** (`project.json`), with nx cache
inputs tracking the spec instead of the unrelated `apiClient` named
input.
- **Regenerate** with the pinned openapi-generator **7.23.0** (committed
client was stale 7.12.0 output — the last client on the old generator).
Zero `sandbox` tokens remain.
- **Drift gate**: drop the analytics exclusion from
`api-client-drift.yml` — it now regenerates + diffs analytics like the
other clients (actionlint clean).
- **eslint**: ignore `**/.nx/**` — the generate target caches its `src/`
output under the gitignored `apps/.nx/cache`, and lint after a local
regen tripped on the cached copy of generated code.

## Verification

- Two-side dashboard typecheck: against the **old** client → 13
rename-mismatch errors (`'ModelsBoxUsage'… Did you mean
'ModelsSandboxUsage'?`); against the **new** client → **zero**
analytics-related errors (remaining 24 are pre-existing, unrelated:
Playground/VNC files + unbuilt `@boxlite-ai/sdk` dist).
- Regen is byte-identical across runs (`--skip-nx-cache`), so the drift
gate won't false-positive.
- `make lint:apps` exit 0.

## Notes

- Runtime risk is nil: the analytics client is only instantiated when
`ANALYTICS_API_URL` is configured, no analytics service exists in the
org today, and the API's own (already-renamed) `box/:boxId/telemetry/*`
endpoints serve the dashboard otherwise (`AnalyticsApiDisabledGuard`
makes the two mutually exclusive).
- Pre-existing, **not** fixed here (scope):
`apps/dashboard/tsconfig.app.json` path mappings (`\"@boxlite-ai/*\":
[\"../../libs/*\"]`) have pointed outside the workspace since the apps
tree was flattened (#460), so raw `tsc -p` never resolved any
`@boxlite-ai/*` import. Worth its own follow-up.

Part of #711.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added AWS Signature V4 support for API requests.
* Telemetry and usage endpoints now operate at box (per-box) and
organization levels (replacing sandbox-level variants).

* **Documentation**
* Comprehensive API docs added/updated with examples for box telemetry,
per-box usage, aggregated usage, and usage chart endpoints.

* **Chores**
* Drift detection updated to include the analytics API client; linting
ignores extended for generated files.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Signed-off-by: dorianzheng <xingzhengde72@gmail.com>
G4614 added a commit that referenced this pull request Jun 12, 2026
Merging the substantive bits of #745 and #746 here so PR #724's
deploy-app-services CI can actually push an otel-collector image:

* otel-collector/builder-config.yaml: fix module paths after #730
  moved everything under apps/ (was: ../../../api-client-go etc.,
  now: ../../../apps/api-client-go). The OCB build was failing with
  `filepath does not exist: /boxlite/otel-collector/exporter`. (#745)
* otel-collector/Dockerfile: apk add python3 make g++ so node-gyp
  can compile any musl-incompatible native add-on during workspace
  `yarn install`. Kept even after dropping dockerode below — small
  safety net in case future deps reintroduce a native build. (#745)
* apps/package.json: drop unused dockerode + @types/dockerode. They
  were inherited from the Daytona import in #460 but never imported
  in source. Drops the dockerode -> docker-modem -> ssh2 ->
  cpu-features chain; cpu-features was the optional native addon
  that forced the toolchain workaround in the first place. (#746)
* apps/yarn.lock: regenerated (-161 lines).
* apps/api-client-go/api/openapi.yaml: regenerate to catch up with
  main's existing `assignedRoleIds.default: []` drift so the
  api-client-drift PR check passes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants