From a049d545a923b512619039b46c81415d57d99406 Mon Sep 17 00:00:00 2001
From: Roomote
Date: Mon, 13 Jul 2026 09:31:54 +0000
Subject: [PATCH] [Refactor] Complete R_ prefix for remaining Roomote-owned env
vars
Rename leftover operator-facing Roomote config (compute selection/Docker
worker policy, preview/tRPC URLs, Slack/source-control app settings, API
thresholds) to R_*, with a DB migration for stored deployment env vars.
---
.agents/skills/mock-slack-testing/SKILL.md | 4 +-
.../references/scenarios.md | 2 +-
.agents/skills/mock-telegram-testing/SKILL.md | 6 +-
.env.local.example | 8 +-
.env.production.example | 40 +--
.roomote/environments/roomote.yaml | 2 +-
SELF_HOSTING.md | 44 ++--
.../__tests__/live-server.integration.test.ts | 2 +-
apps/api/src/__tests__/logging.test.ts | 8 +-
.../src/handlers/ado/__tests__/index.test.ts | 2 +-
apps/api/src/handlers/ado/index.ts | 2 +-
.../bitbucket/__tests__/index.test.ts | 2 +-
apps/api/src/handlers/bitbucket/index.ts | 2 +-
.../handlers/gitea/__tests__/index.test.ts | 2 +-
apps/api/src/handlers/gitea/index.ts | 2 +-
.../__tests__/handlePushConflictCheck.test.ts | 2 +-
.../handlers/gitlab/__tests__/index.test.ts | 2 +-
apps/api/src/handlers/gitlab/index.ts | 4 +-
.../src/handlers/health/__tests__/api.test.ts | 2 +-
.../health/__tests__/controller.test.ts | 2 +-
apps/api/src/handlers/health/api.ts | 2 +-
apps/api/src/handlers/health/controller.ts | 2 +-
.../linear-active-run-priority.test.ts | 2 +-
.../linear-routing-confirmation.test.ts | 4 +-
apps/api/src/handlers/linear/index.ts | 2 +-
apps/api/src/handlers/mcp/github.ts | 2 +-
apps/api/src/handlers/slack/constants.ts | 2 +-
.../channel-auto-start-unlinked.test.ts | 2 +-
.../message-entry-unmentioned-routing.test.ts | 2 +-
.../handlers/slack/events/message-entry.ts | 2 +-
.../handlers/teams/__tests__/index.test.ts | 2 +-
apps/api/src/handlers/teams/index.ts | 2 +-
.../handlers/telegram/__tests__/index.test.ts | 4 +-
.../handlers/telegram/task-orchestration.ts | 2 +-
apps/api/src/logging.ts | 4 +-
.../requestObservabilityMiddleware.test.ts | 6 +-
.../requestObservabilityMiddleware.ts | 2 +-
apps/api/src/server.ts | 8 +-
.../__tests__/webhook-cleanup.test.ts | 4 +-
.../scheduled-jobs/standby-retention.test.ts | 8 +-
.../src/scheduled-jobs/webhook-cleanup.ts | 4 +-
apps/controller/src/BaseController.ts | 2 +-
apps/controller/src/RoomoteController.ts | 24 +-
.../src/__tests__/RoomoteController.test.ts | 54 ++---
apps/controller/src/__tests__/utils.test.ts | 26 +-
.../__tests__/spawn-modal-worker.test.ts | 2 +-
.../compute-providers/spawn-docker-worker.ts | 22 +-
apps/controller/src/utils.ts | 4 +-
apps/dev/src/index.ts | 6 +-
.../dev/src/services/__tests__/docker.test.ts | 4 +-
.../src/services/__tests__/ecosystem.test.ts | 16 +-
apps/dev/src/services/__tests__/pm2.test.ts | 8 +-
apps/dev/src/services/docker.ts | 5 +-
apps/dev/src/services/pm2.ts | 4 +-
apps/docs/environment-variables.mdx | 132 +++++-----
apps/docs/providers/communications/slack.mdx | 2 +-
apps/docs/providers/compute/blaxel.mdx | 4 +-
apps/docs/providers/compute/docker.mdx | 42 ++--
.../providers/source-control/azure-devops.mdx | 8 +-
.../providers/source-control/bitbucket.mdx | 16 +-
apps/docs/providers/source-control/gitea.mdx | 10 +-
apps/docs/providers/source-control/gitlab.mdx | 10 +-
apps/preview-proxy/src/__tests__/fixtures.ts | 4 +-
apps/preview-proxy/src/config.ts | 2 +-
.../src/handlers/auth-callback.ts | 2 +-
apps/preview-proxy/src/handlers/http.ts | 2 +-
.../src/lib/__tests__/cookies.test.ts | 28 ++-
apps/preview-proxy/src/lib/cookies.ts | 2 +-
apps/preview-proxy/src/lib/url-parser.ts | 2 +-
apps/web/next.config.ts | 4 +-
.../setup/StepComputeConfig.client.test.tsx | 6 +-
.../(onboarding)/setup/StepComputeConfig.tsx | 4 +-
.../setup/StepComputeProvider.client.test.tsx | 2 +-
.../setup/StepSourceControlConfig.tsx | 2 +-
apps/web/src/app/api/auth/preview/route.ts | 10 +-
.../app/api/caddy/ask/__tests__/route.test.ts | 8 +-
apps/web/src/app/api/caddy/ask/route.ts | 4 +-
.../api/teams/auth/__tests__/route.test.ts | 2 +-
apps/web/src/app/api/teams/auth/route.ts | 2 +-
.../[...path]/__tests__/route.test.ts | 4 +-
.../src/app/api/webhooks/[...path]/route.ts | 2 +-
.../ComputeProviderSection.client.test.tsx | 4 +-
.../settings/ComputeProviderSection.tsx | 2 +-
.../settings/SourceControl.test.tsx | 2 +-
.../src/components/settings/SourceControl.tsx | 2 +-
.../LivePreviewsSettings.client.test.tsx | 4 +-
.../previews/LivePreviewsSettings.tsx | 2 +-
.../__tests__/better-auth-base-url.test.ts | 6 +-
.../__tests__/browser-origin-trust.test.ts | 6 +-
apps/web/src/lib/server/__tests__/env.test.ts | 10 +-
.../auth-provider-config.client.test.ts | 40 +--
.../src/lib/server/auth-provider-config.ts | 46 ++--
apps/web/src/lib/server/auth.ts | 4 +-
apps/web/src/lib/server/preview-session.ts | 8 +-
apps/web/src/lib/server/slack-redirect-uri.ts | 2 +-
.../commands/compute/compute-provisioning.ts | 2 +-
.../src/trpc/commands/compute/index.test.ts | 42 ++--
apps/web/src/trpc/commands/compute/index.ts | 8 +-
.../web/src/trpc/commands/deployment/index.ts | 2 +-
.../trpc/commands/github/mutations.test.ts | 2 +-
.../web/src/trpc/commands/github/mutations.ts | 6 +-
.../src/trpc/commands/misc-settings/index.ts | 18 +-
.../trpc/commands/preview-settings/index.ts | 12 +-
.../trpc/commands/sandbox-session/index.ts | 4 +-
.../src/trpc/commands/setup-new/index.test.ts | 34 +--
apps/web/src/trpc/commands/setup-new/index.ts | 8 +-
apps/web/src/trpc/commands/setup/index.ts | 2 +-
apps/web/src/trpc/commands/slack/index.ts | 2 +-
.../commands/source-control/index.test.ts | 12 +-
.../src/trpc/commands/source-control/index.ts | 42 ++--
.../src/trpc/commands/task-runs/index.test.ts | 2 +-
apps/web/src/trpc/commands/task-runs/index.ts | 2 +-
.../src/commands/__tests__/snapshot.test.ts | 2 +-
.../src/commands/__tests__/utils.test.ts | 32 +--
.../setup/__tests__/setup-mcps.test.ts | 24 +-
apps/worker/src/commands/setup/setup-mcps.ts | 4 +-
apps/worker/src/commands/utils/env-vars.ts | 8 +-
.../src/env/__tests__/substitute.test.ts | 11 +-
.../src/env/__tests__/worker-env.test.ts | 52 ++--
apps/worker/src/env/worker-env.ts | 12 +-
.../__tests__/config.test.ts | 6 +-
.../src/mcp/roomote-mcp-server/config.ts | 2 +-
apps/worker/src/monitoring/sentry.test.ts | 2 +-
.../src/monitoring/worker-release-metadata.ts | 2 +-
apps/worker/src/run-task/env.ts | 4 +-
.../src/run-task/polling/cancel.test.ts | 8 +-
.../src/run-task/polling/linear.test.ts | 8 +-
.../run-task/polling/poll-error-context.ts | 2 +-
.../worker/src/run-task/polling/slack.test.ts | 8 +-
deploy/README.md | 36 +--
deploy/ci/deployment-smoke.sh | 16 +-
deploy/ci/docker-compose.ci.yml | 2 +-
deploy/ci/upgrade-compatibility.sh | 14 +-
deploy/ci/validate-deployment-artifacts.mjs | 4 +-
deploy/compose/docker-compose.prod.yml | 86 +++----
deploy/coolify/README.md | 28 +--
deploy/coolify/docker-compose.yaml | 20 +-
deploy/deployment-catalog.json | 2 +-
deploy/fly/README.md | 16 +-
deploy/fly/fly.toml | 14 +-
deploy/host/roomote | 14 +-
.../host/tests/backup-restore.integration.sh | 4 +-
deploy/install.sh | 14 +-
deploy/railway/README.md | 44 ++--
deploy/railway/template.yaml | 28 +--
deploy/render/README.md | 30 +--
deploy/scripts/deploy.sh | 14 +-
deploy/scripts/upgrade.sh | 22 +-
docker-compose.compute-docker.yml | 36 +--
docker-compose.production.yml | 18 +-
docker-compose.self-host.yml | 20 +-
ecosystem.config.js | 23 +-
packages/ado/src/__tests__/api.test.ts | 26 +-
packages/ado/src/api.ts | 22 +-
.../auth/src/__tests__/sandbox-oidc.test.ts | 2 +-
packages/auth/src/sandbox-oidc.ts | 2 +-
packages/bitbucket/src/api.ts | 8 +-
.../src/server/__tests__/shared-utils.test.ts | 2 +-
.../cloud-agents/src/server/shared-utils.ts | 2 +-
.../evals/run-telegram-scenario.ts | 8 +-
.../scripts/run-mock-telegram.ts | 2 +-
.../thread-reply-footer-context.test.ts | 4 +-
.../src/telegram-api-base-url.ts | 6 +-
.../src/thread-reply-footer-context.ts | 4 +-
.../src/__tests__/environment-machine.test.ts | 10 +-
.../src/__tests__/factory.test.ts | 8 +-
packages/compute-providers/src/factory.ts | 6 +-
.../src/sandbox/worker-release-selection.ts | 6 +-
.../src/worker-env/__tests__/base.test.ts | 12 +-
.../compute-providers/src/worker-env/base.ts | 6 +-
.../0010_more_canonical_r_env_vars.sql | 228 ++++++++++++++++++
packages/db/drizzle/meta/_journal.json | 7 +
.../__tests__/compute-runtime-config.test.ts | 18 +-
packages/db/src/lib/compute-runtime-config.ts | 18 +-
packages/db/src/lib/preview-runtime-config.ts | 12 +-
.../db/src/lib/router-debug-settings.test.ts | 4 +-
packages/db/src/lib/router-debug-settings.ts | 6 +-
.../lib/telegram-runtime-credentials.test.ts | 2 +-
.../src/lib/telegram-runtime-credentials.ts | 2 +-
packages/db/src/lib/webhook-retention.ts | 2 +-
packages/env/src/__tests__/index.test.ts | 166 +++++++------
packages/env/src/app-env.ts | 4 +-
packages/env/src/index.ts | 179 ++++++++------
packages/gitea/src/__tests__/api.test.ts | 20 +-
packages/gitea/src/api.ts | 24 +-
.../src/__tests__/is-repo-skipped.test.ts | 36 +--
packages/github/src/is-repo-skipped.ts | 8 +-
packages/gitlab/src/__tests__/api.test.ts | 36 +--
packages/gitlab/src/api.ts | 4 +-
packages/sdk/src/client/index.ts | 6 +-
.../__tests__/conflict-scan.test.ts | 2 +-
.../source-control-pull-request-reads.ts | 6 +-
.../source-control-pull-request-writes.ts | 6 +-
.../source-control-pull-requests.ts | 4 +-
.../__tests__/dequeue-helpers.test.ts | 4 +-
packages/slack/evals/run-slack-scenario.ts | 8 +-
packages/slack/scripts/run-mock-slack.ts | 2 +-
packages/slack/src/__tests__/logging.test.ts | 22 +-
.../__tests__/mcp-setup-interruption.test.ts | 2 +-
.../slack/src/__tests__/router-debug.test.ts | 2 +-
.../__tests__/show-task-configuration.test.ts | 2 +-
.../src/__tests__/slack-api-base-url.test.ts | 10 +-
.../src/__tests__/slack-notifier.test.ts | 12 +-
.../start-auto-routed-slack-task.test.ts | 2 +-
.../slack/src/__tests__/thread-footer.test.ts | 4 +-
.../slack/src/__tests__/web-client.test.ts | 14 +-
packages/slack/src/block-kit.ts | 6 +-
packages/slack/src/logging.ts | 4 +-
packages/slack/src/slack-api-base-url.ts | 4 +-
.../slack/src/start-auto-routed-slack-task.ts | 2 +-
packages/slack/src/web-client.ts | 2 +-
.../types/src/control-plane-env-vars.test.ts | 8 +-
packages/types/src/control-plane-env-vars.ts | 8 +-
packages/types/src/preview-proxy.ts | 16 +-
packages/types/src/setup-auth-config.test.ts | 2 +-
.../types/src/setup-compute-config.test.ts | 54 ++---
packages/types/src/setup-compute-config.ts | 32 +--
.../src/setup-source-control-config.test.ts | 24 +-
.../types/src/setup-source-control-config.ts | 88 +++----
render.yaml | 14 +-
turbo.json | 2 +-
221 files changed, 1679 insertions(+), 1338 deletions(-)
create mode 100644 packages/db/drizzle/0010_more_canonical_r_env_vars.sql
diff --git a/.agents/skills/mock-slack-testing/SKILL.md b/.agents/skills/mock-slack-testing/SKILL.md
index afb316c7..1c9c7c51 100644
--- a/.agents/skills/mock-slack-testing/SKILL.md
+++ b/.agents/skills/mock-slack-testing/SKILL.md
@@ -1,6 +1,6 @@
---
name: mock-slack-testing
-description: Run Roomote Slack integration flows through the existing mock Slack harness instead of a real Slack workspace. Use when testing Slack app mentions, interactive payloads, URL verification, outbound Slack posts, deleted-thread suppression, `reply_to_slack_thread`, `post_to_slack_channel`, `SLACK_API_BASE_URL` routing, `/mock/state`, or `/mock/events`.
+description: Run Roomote Slack integration flows through the existing mock Slack harness instead of a real Slack workspace. Use when testing Slack app mentions, interactive payloads, URL verification, outbound Slack posts, deleted-thread suppression, `reply_to_slack_thread`, `post_to_slack_channel`, `R_SLACK_API_BASE_URL` routing, `/mock/state`, or `/mock/events`.
---
# Mock Slack Testing
@@ -84,7 +84,7 @@ When testing flows where Roomote posts back to Slack (thread replies, channel po
```bash
# Set in the API server's environment before it starts, or inject at runtime
-SLACK_API_BASE_URL=http://127.0.0.1:3012/api/
+R_SLACK_API_BASE_URL=http://127.0.0.1:3012/api/
```
The signing secret is read from `Env.SLACK_SIGNING_SECRET` via dotenvx. The harness uses the same secret by default, so inbound replay signatures will match without extra configuration.
diff --git a/.agents/skills/mock-slack-testing/references/scenarios.md b/.agents/skills/mock-slack-testing/references/scenarios.md
index 698da96a..cc0a5087 100644
--- a/.agents/skills/mock-slack-testing/references/scenarios.md
+++ b/.agents/skills/mock-slack-testing/references/scenarios.md
@@ -65,7 +65,7 @@ Use this file to choose the closest scenario before creating a custom one.
- Use for: worker or API code that should post a reply into an existing Slack thread.
- Minimum setup:
- a thread root in state
- - the Roomote service under test pointed at the harness via `SLACK_API_BASE_URL`
+ - the Roomote service under test pointed at the harness via `R_SLACK_API_BASE_URL`
- Drive with: the real Roomote code path that emits the outbound Slack call
- Success looks like:
- the expected reply appears in `/mock/state`
diff --git a/.agents/skills/mock-telegram-testing/SKILL.md b/.agents/skills/mock-telegram-testing/SKILL.md
index 3c8a8b5d..b659df74 100644
--- a/.agents/skills/mock-telegram-testing/SKILL.md
+++ b/.agents/skills/mock-telegram-testing/SKILL.md
@@ -1,6 +1,6 @@
---
name: mock-telegram-testing
-description: Run Roomote Telegram integration flows through the checked-in mock Telegram Bot API harness instead of a real Telegram bot. Use when testing Telegram task entry, follow-up queueing to active jobs, the `/new` command, callback buttons, outbound Telegram posts, reply footers, message chunking, `TELEGRAM_API_BASE_URL` routing, `/mock/state`, or `/mock/events`.
+description: Run Roomote Telegram integration flows through the checked-in mock Telegram Bot API harness instead of a real Telegram bot. Use when testing Telegram task entry, follow-up queueing to active jobs, the `/new` command, callback buttons, outbound Telegram posts, reply footers, message chunking, `R_TELEGRAM_API_BASE_URL` routing, `/mock/state`, or `/mock/events`.
---
# Mock Telegram Testing
@@ -15,7 +15,7 @@ Telegram continuity is inferred from **chat id (+ forum topic id) → active clo
| ----------------------------- | ----------------------------------------------------------- |
| Harness port | `3013` |
| Harness base URL | `http://127.0.0.1:3013` |
-| Telegram API base for Roomote | `TELEGRAM_API_BASE_URL=http://127.0.0.1:3013` |
+| Telegram API base for Roomote | `R_TELEGRAM_API_BASE_URL=http://127.0.0.1:3013` |
| API webhook endpoint | `http://localhost:3001/api/webhooks/telegram` |
| Mock state endpoint | `http://127.0.0.1:3013/mock/state` |
| Mock event replay endpoint | `http://127.0.0.1:3013/mock/events` |
@@ -54,7 +54,7 @@ The API resolves Telegram credentials from real env vars first (`resolveTelegram
```bash
R_TELEGRAM_BOT_TOKEN=7000000001:mock-telegram-token # any value; the harness accepts all tokens unless acceptedBotTokens is set
R_TELEGRAM_WEBHOOK_SECRET= # harness reads the same var via dotenvx, so signatures match automatically
-TELEGRAM_API_BASE_URL=http://127.0.0.1:3013 # reroutes ALL outbound Bot API calls to the harness
+R_TELEGRAM_API_BASE_URL=http://127.0.0.1:3013 # reroutes ALL outbound Bot API calls to the harness
```
Roomote resolves `roomote_mock_bot` from the harness's `getMe` response using
diff --git a/.env.local.example b/.env.local.example
index 23b40f04..17caebcc 100644
--- a/.env.local.example
+++ b/.env.local.example
@@ -52,9 +52,9 @@
# R_GITHUB_CLIENT_SECRET=
# R_GITHUB_WEBHOOK_SECRET=
# GITLAB_TOKEN=
-# GITLAB_WEBHOOK_SECRET=
-# GITLAB_WEBHOOK_SIGNING_TOKEN=
-# SLACK_APP_ID=
+# R_GITLAB_WEBHOOK_SECRET=
+# R_GITLAB_WEBHOOK_SIGNING_TOKEN=
+# R_SLACK_APP_ID=
# R_SLACK_CLIENT_ID=
# R_SLACK_CLIENT_SECRET=
# R_SLACK_SIGNING_SECRET=
@@ -74,4 +74,4 @@
# R_LINEAR_WEBHOOK_SECRET=
# Optional: override GitHub automated processing for local testing.
-# GITHUB_AUTOMATED_SKIP_REPOS=
+# R_GITHUB_AUTOMATED_SKIP_REPOS=
diff --git a/.env.production.example b/.env.production.example
index e3b93e18..71dacac1 100644
--- a/.env.production.example
+++ b/.env.production.example
@@ -16,7 +16,7 @@ ROOMOTE_PREVIEW_DOMAIN=preview.roomote.example.com
# Replace local defaults before exposing a shared deployment.
DATABASE_URL=postgres://postgres:password@postgres:5432/roomote_development
REDIS_URL=redis://redis:6379
-TRPC_URL=https://roomote.example.com/_roomote-api
+R_TRPC_URL=https://roomote.example.com/_roomote-api
S3_ENDPOINT=http://minio:9000
# Production Caddy routes /$S3_BUCKET_ARTIFACTS/* to MinIO, so hosted workers
# can upload artifacts through the public app origin without a separate object
@@ -73,29 +73,29 @@ OPENROUTER_API_KEY=
# Compute provider. Use docker for a single-host deployment, or
# sandbox/modal/daytona for a hosted worker runtime.
-DEFAULT_COMPUTE_PROVIDER=docker
+R_DEFAULT_COMPUTE_PROVIDER=docker
# Optional on published app images: when unset, the app derives the worker
# image as ghcr.io/roocodeinc/roomote-worker:.
# Override ROOMOTE_WORKER_IMAGE_REPO to derive from a fork or mirror repo.
-# DOCKER_WORKER_IMAGE=roomote-worker:local
+# R_DOCKER_WORKER_IMAGE=roomote-worker:local
# ROOMOTE_WORKER_IMAGE_REPO=ghcr.io/roocodeinc/roomote-worker
-# DOCKER_WORKER_PLATFORM=linux/amd64
-# DOCKER_WORKER_NETWORK=roomote_worker
-# DOCKER_WORKER_RELEASE_PATH=/roomote/releases/worker-current.tar.gz
-# DOCKER_WORKER_CPU_LIMIT=2
-# DOCKER_WORKER_MEMORY_LIMIT=4g
-# DOCKER_WORKER_PIDS_LIMIT=512
-# DOCKER_WORKER_DISK_LIMIT=20g
-# DOCKER_WORKER_ALLOW_UNBOUNDED_DISK=false
-# DOCKER_WORKER_LOG_MAX_SIZE=10m
-# DOCKER_WORKER_LOG_MAX_FILES=3
-# DOCKER_WORKER_EGRESS_POLICY=internet
-# DOCKER_STANDBY_MAX_COUNT=10
-# DOCKER_STANDBY_MAX_AGE_HOURS=24
+# R_DOCKER_WORKER_PLATFORM=linux/amd64
+# R_DOCKER_WORKER_NETWORK=roomote_worker
+# R_DOCKER_WORKER_RELEASE_PATH=/roomote/releases/worker-current.tar.gz
+# R_DOCKER_WORKER_CPU_LIMIT=2
+# R_DOCKER_WORKER_MEMORY_LIMIT=4g
+# R_DOCKER_WORKER_PIDS_LIMIT=512
+# R_DOCKER_WORKER_DISK_LIMIT=20g
+# R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK=false
+# R_DOCKER_WORKER_LOG_MAX_SIZE=10m
+# R_DOCKER_WORKER_LOG_MAX_FILES=3
+# R_DOCKER_WORKER_EGRESS_POLICY=internet
+# R_DOCKER_STANDBY_MAX_COUNT=10
+# R_DOCKER_STANDBY_MAX_AGE_HOURS=24
# MODAL_TOKEN_ID=
# MODAL_TOKEN_SECRET=
# Leave blank: the installer and deployer keep it in sync with the matching
-# DOCKER_WORKER_IMAGE, and on published app images the app derives it from
+# R_DOCKER_WORKER_IMAGE, and on published app images the app derives it from
# the effective worker image at runtime, so Modal only needs the token pair.
# Set a value only to pin a custom base image; it is then never overwritten.
# MODAL_BASE_IMAGE_REF=
@@ -126,11 +126,11 @@ DEFAULT_COMPUTE_PROVIDER=docker
# R_GITHUB_CLIENT_SECRET=
# R_GITHUB_WEBHOOK_SECRET=
# GITLAB_TOKEN=
-# GITLAB_WEBHOOK_SECRET=
-# GITLAB_WEBHOOK_SIGNING_TOKEN=
+# R_GITLAB_WEBHOOK_SECRET=
+# R_GITLAB_WEBHOOK_SIGNING_TOKEN=
# Optional integrations.
-# SLACK_APP_ID=
+# R_SLACK_APP_ID=
# R_SLACK_SIGNING_SECRET=
# R_TELEGRAM_BOT_TOKEN=
# R_TELEGRAM_WEBHOOK_SECRET=
diff --git a/.roomote/environments/roomote.yaml b/.roomote/environments/roomote.yaml
index 47823fee..ed546d96 100644
--- a/.roomote/environments/roomote.yaml
+++ b/.roomote/environments/roomote.yaml
@@ -101,7 +101,7 @@ repositories:
detached: true
logfile: /tmp/roomote-bullmq.log
- name: Start controller
- run: R_APP_ENV=development TRPC_URL="${ROOMOTE_API_HOST:-http://localhost:13001}" R_APP_URL="${ROOMOTE_WEB_HOST:-http://localhost:3000}" R_PUBLIC_URL="${ROOMOTE_WEB_HOST:-http://localhost:3000}" pnpm --filter @roomote/controller dev
+ run: R_APP_ENV=development R_TRPC_URL="${ROOMOTE_API_HOST:-http://localhost:13001}" R_APP_URL="${ROOMOTE_WEB_HOST:-http://localhost:3000}" R_PUBLIC_URL="${ROOMOTE_WEB_HOST:-http://localhost:3000}" pnpm --filter @roomote/controller dev
timeout: 600
continue_on_error: true
detached: true
diff --git a/SELF_HOSTING.md b/SELF_HOSTING.md
index 08aa6356..639d91ac 100644
--- a/SELF_HOSTING.md
+++ b/SELF_HOSTING.md
@@ -143,7 +143,7 @@ healthy upgrade remains in place and the command prints a warning.
The domain is baked into OAuth apps created during setup (GitHub App, Slack
app, sign-in provider redirects). To move a deployment to a new domain: update
-DNS, set `ROOMOTE_APP_DOMAIN`, `ROOMOTE_PREVIEW_DOMAIN`, and `TRPC_URL` in
+DNS, set `ROOMOTE_APP_DOMAIN`, `ROOMOTE_PREVIEW_DOMAIN`, and `R_TRPC_URL` in
`/opt/roomote/.env`, run `roomote up`, then re-create or update the GitHub App
and Slack app (both use manifest flows, so this is a few clicks) and update
your sign-in provider's redirect URLs.
@@ -215,7 +215,7 @@ For production, set domains in `.env.production`:
```sh
ROOMOTE_APP_DOMAIN=roomote.example.com
ROOMOTE_PREVIEW_DOMAIN=preview.roomote.example.com
-TRPC_URL=https://roomote.example.com/_roomote-api
+R_TRPC_URL=https://roomote.example.com/_roomote-api
```
The production Compose overlay derives these runtime URLs:
@@ -224,9 +224,9 @@ The production Compose overlay derives these runtime URLs:
| ------------------------ | ------------------------------------------ |
| `R_PUBLIC_URL` | `https://$ROOMOTE_APP_DOMAIN` |
| `R_APP_URL` | `https://$ROOMOTE_APP_DOMAIN` |
-| `TRPC_URL` | `https://$ROOMOTE_APP_DOMAIN/_roomote-api` |
-| `PREVIEW_PROXY_BASE_URL` | `https://$ROOMOTE_PREVIEW_DOMAIN` |
-| `PREVIEW_DOMAINS` | `$ROOMOTE_PREVIEW_DOMAIN` |
+| `R_TRPC_URL` | `https://$ROOMOTE_APP_DOMAIN/_roomote-api` |
+| `R_PREVIEW_PROXY_BASE_URL` | `https://$ROOMOTE_PREVIEW_DOMAIN` |
+| `R_PREVIEW_DOMAINS` | `$ROOMOTE_PREVIEW_DOMAIN` |
On production Caddy, `ROOMOTE_APP_DOMAIN` serves both the web app and the
worker-facing API. Caddy routes the explicit `/_roomote-api/*` prefix to
@@ -445,7 +445,7 @@ R_SLACK_CLIENT_SECRET=...
R_SLACK_SIGNING_SECRET=...
```
-`SLACK_APP_ID` is optional for manual preconfiguration. Roomote saves Slack's
+`R_SLACK_APP_ID` is optional for manual preconfiguration. Roomote saves Slack's
`app_id` from the OAuth installation response after the workspace install flow
completes.
@@ -518,7 +518,7 @@ auth as user `admin`. This is gated on the password, not on `NODE_ENV`, so the
`DASHBOARD_PASSWORD`, `/admin` returns `503` and the dashboard is disabled while
the queue workers keep running.
-`PREVIEW_PROXY_BASE_URL` and `PREVIEW_DOMAINS` no longer block startup when
+`R_PREVIEW_PROXY_BASE_URL` and `R_PREVIEW_DOMAINS` no longer block startup when
unset: live previews simply report as not configured until you set the values
via env or from **Settings → Live Previews**.
@@ -562,33 +562,33 @@ docker compose --env-file .env.production \
## Compute Provider
-`DEFAULT_COMPUTE_PROVIDER=docker` runs task workers as sibling Docker
+`R_DEFAULT_COMPUTE_PROVIDER=docker` runs task workers as sibling Docker
containers on the same host. Include `docker-compose.compute-docker.yml` when
using Docker sandboxes. That overlay:
-- builds the `DOCKER_WORKER_IMAGE` from `apps/worker/Dockerfile`;
+- builds the `R_DOCKER_WORKER_IMAGE` from `apps/worker/Dockerfile`;
- mounts `/var/run/docker.sock` into the controller;
-- sets `DOCKER_WORKER_NETWORK=roomote_worker` as the trusted-service discovery
+- sets `R_DOCKER_WORKER_NETWORK=roomote_worker` as the trusted-service discovery
network; each worker receives its own network containing only that worker,
the API, and the optional preview proxy when enabled, never sibling tasks,
Postgres, Redis, or MinIO;
- enforces CPU, memory, PID, supported writable-layer quotas, and log limits
and blocks private and cloud metadata ranges under the default `internet`
egress policy;
-- points `DOCKER_WORKER_RELEASE_PATH` at the controller image's packaged worker
+- points `R_DOCKER_WORKER_RELEASE_PATH` at the controller image's packaged worker
release archive.
Docker tasks fail closed when the host storage driver cannot enforce
-`DOCKER_WORKER_DISK_LIMIT`. Set `DOCKER_WORKER_ALLOW_UNBOUNDED_DISK=true` only
+`R_DOCKER_WORKER_DISK_LIMIT`. Set `R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK=true` only
when the host already provides an equivalent storage quota outside Docker.
Docker sandboxes are the simplest single-host deployment path:
```sh
-DEFAULT_COMPUTE_PROVIDER=docker
-DOCKER_WORKER_IMAGE=roomote-worker:local
+R_DEFAULT_COMPUTE_PROVIDER=docker
+R_DOCKER_WORKER_IMAGE=roomote-worker:local
# Optional: defaults to the host architecture (linux/amd64 or linux/arm64)
-DOCKER_WORKER_PLATFORM=linux/amd64
+R_DOCKER_WORKER_PLATFORM=linux/amd64
```
If you do not want the controller to access the host Docker socket, choose a
@@ -596,24 +596,24 @@ hosted provider instead and omit `docker-compose.compute-docker.yml`:
```sh
# Modal
-DEFAULT_COMPUTE_PROVIDER=modal
+R_DEFAULT_COMPUTE_PROVIDER=modal
MODAL_TOKEN_ID=...
MODAL_TOKEN_SECRET=...
# Optional on published app images — see the worker image note below.
MODAL_BASE_IMAGE_REF=...
# E2B
-DEFAULT_COMPUTE_PROVIDER=e2b
+R_DEFAULT_COMPUTE_PROVIDER=e2b
E2B_API_KEY=...
E2B_TEMPLATE_ID=...
# Daytona
-DEFAULT_COMPUTE_PROVIDER=daytona
+R_DEFAULT_COMPUTE_PROVIDER=daytona
DAYTONA_API_KEY=...
DAYTONA_SNAPSHOT_NAME=...
# Blaxel
-DEFAULT_COMPUTE_PROVIDER=blaxel
+R_DEFAULT_COMPUTE_PROVIDER=blaxel
BL_API_KEY=...
BL_WORKSPACE=...
# Optional prebuilt Blaxel image override
@@ -621,18 +621,18 @@ BLAXEL_IMAGE=sandbox/roomote-worker:
```
`E2B_TEMPLATE_ID`, `DAYTONA_SNAPSHOT_NAME`, and `BLAXEL_IMAGE` can also be provisioned
-automatically during setup when a registry-qualified `DOCKER_WORKER_IMAGE`
+automatically during setup when a registry-qualified `R_DOCKER_WORKER_IMAGE`
is configured — the setup wizard and the Settings → Sandboxes page build the
worker base artifact in your provider account after credentials are saved.
For quick-install and V1 deployer-managed hosts, leave `MODAL_BASE_IMAGE_REF`
blank unless you need a custom Modal image. The installer and deployer sync it
-to the matching `DOCKER_WORKER_IMAGE` (the published worker image doubles as
+to the matching `R_DOCKER_WORKER_IMAGE` (the published worker image doubles as
the Modal base image) and preserve a different non-empty value as an explicit
override.
When running the published `ghcr.io/roocodeinc/roomote-app` image, both
-`DOCKER_WORKER_IMAGE` and `MODAL_BASE_IMAGE_REF` are optional: if unset, the
+`R_DOCKER_WORKER_IMAGE` and `MODAL_BASE_IMAGE_REF` are optional: if unset, the
app derives `ghcr.io/roocodeinc/roomote-worker:` from the release
version baked into the app image, which the publish workflow tags in lockstep
with the worker image. Set `ROOMOTE_WORKER_IMAGE_REPO` to derive from a
diff --git a/apps/api/src/__tests__/live-server.integration.test.ts b/apps/api/src/__tests__/live-server.integration.test.ts
index 8d21f4c5..5863eae4 100644
--- a/apps/api/src/__tests__/live-server.integration.test.ts
+++ b/apps/api/src/__tests__/live-server.integration.test.ts
@@ -5,7 +5,7 @@ vi.mock('@roomote/env', async (importOriginal) => {
...actual,
Env: {
...actual.Env,
- API_SLOW_REQUEST_THRESHOLD_MS: 0,
+ R_API_SLOW_REQUEST_THRESHOLD_MS: 0,
},
};
});
diff --git a/apps/api/src/__tests__/logging.test.ts b/apps/api/src/__tests__/logging.test.ts
index 0c04578e..bb924f39 100644
--- a/apps/api/src/__tests__/logging.test.ts
+++ b/apps/api/src/__tests__/logging.test.ts
@@ -2,14 +2,14 @@ const { envMock } = vi.hoisted(() => ({
envMock: {
APP_ENV: 'production',
NODE_ENV: 'production',
- API_DEBUG_LOGS: undefined as string | undefined,
+ R_API_DEBUG_LOGS: undefined as string | undefined,
},
}));
async function importLoggingModule(envOverrides?: {
APP_ENV?: string;
NODE_ENV?: string;
- API_DEBUG_LOGS?: string | undefined;
+ R_API_DEBUG_LOGS?: string | undefined;
}) {
vi.resetModules();
vi.doMock('@roomote/env', async (importOriginal) => {
@@ -20,7 +20,7 @@ async function importLoggingModule(envOverrides?: {
Env: Object.assign(envMock, {
APP_ENV: 'production',
NODE_ENV: 'production',
- API_DEBUG_LOGS: undefined,
+ R_API_DEBUG_LOGS: undefined,
...envOverrides,
}),
};
@@ -81,7 +81,7 @@ describe('API logging helpers', () => {
const baselineCallCount = debugSpy.mock.calls.length;
apiLogger.debug('[GitHub] disabled');
- envMock.API_DEBUG_LOGS = '1';
+ envMock.R_API_DEBUG_LOGS = '1';
apiLogger.debug('[GitHub] enabled later');
const newCalls = debugSpy.mock.calls.slice(baselineCallCount);
diff --git a/apps/api/src/handlers/ado/__tests__/index.test.ts b/apps/api/src/handlers/ado/__tests__/index.test.ts
index 92d0f066..783bd725 100644
--- a/apps/api/src/handlers/ado/__tests__/index.test.ts
+++ b/apps/api/src/handlers/ado/__tests__/index.test.ts
@@ -45,7 +45,7 @@ describe('ado webhook router', () => {
mockRecordWebhook.mockReset();
mockResolveDeploymentEnvVar.mockReset();
mockResolveDeploymentEnvVar.mockImplementation(async (name: string) =>
- name === 'ADO_WEBHOOK_SECRET' ? 'ado-secret' : null,
+ name === 'R_ADO_WEBHOOK_SECRET' ? 'ado-secret' : null,
);
mockHandleAdoComment.mockResolvedValue({ status: 'ok' });
mockHandleAdoPullRequest.mockResolvedValue({ status: 'ok' });
diff --git a/apps/api/src/handlers/ado/index.ts b/apps/api/src/handlers/ado/index.ts
index f729a568..358aa277 100644
--- a/apps/api/src/handlers/ado/index.ts
+++ b/apps/api/src/handlers/ado/index.ts
@@ -81,7 +81,7 @@ ado.post('/', async (c) => {
try {
const headers = c.req.header();
const body = await c.req.text();
- const secretToken = await resolveDeploymentEnvVar('ADO_WEBHOOK_SECRET');
+ const secretToken = await resolveDeploymentEnvVar('R_ADO_WEBHOOK_SECRET');
const verified = verifyAdoWebhook({
headers,
secretToken: secretToken ?? undefined,
diff --git a/apps/api/src/handlers/bitbucket/__tests__/index.test.ts b/apps/api/src/handlers/bitbucket/__tests__/index.test.ts
index 50796268..a60392a7 100644
--- a/apps/api/src/handlers/bitbucket/__tests__/index.test.ts
+++ b/apps/api/src/handlers/bitbucket/__tests__/index.test.ts
@@ -52,7 +52,7 @@ describe('bitbucket webhook router', () => {
handleBitbucketPullRequest.mockClear();
handleBitbucketComment.mockClear();
resolveDeploymentEnvVar.mockImplementation(async (name: string) =>
- name === 'BITBUCKET_WEBHOOK_SECRET' ? 'bitbucket-secret' : null,
+ name === 'R_BITBUCKET_WEBHOOK_SECRET' ? 'bitbucket-secret' : null,
);
});
diff --git a/apps/api/src/handlers/bitbucket/index.ts b/apps/api/src/handlers/bitbucket/index.ts
index 1e7f2bf7..4dbb069d 100644
--- a/apps/api/src/handlers/bitbucket/index.ts
+++ b/apps/api/src/handlers/bitbucket/index.ts
@@ -53,7 +53,7 @@ bitbucket.post('/', async (c) => {
const headers = c.req.header();
const body = await c.req.text();
const secretToken = await resolveDeploymentEnvVar(
- 'BITBUCKET_WEBHOOK_SECRET',
+ 'R_BITBUCKET_WEBHOOK_SECRET',
);
const verified = verifyBitbucketWebhook({
body,
diff --git a/apps/api/src/handlers/gitea/__tests__/index.test.ts b/apps/api/src/handlers/gitea/__tests__/index.test.ts
index fca8e771..4cf4cdb3 100644
--- a/apps/api/src/handlers/gitea/__tests__/index.test.ts
+++ b/apps/api/src/handlers/gitea/__tests__/index.test.ts
@@ -51,7 +51,7 @@ describe('gitea webhook router', () => {
mockRecordWebhook.mockReset();
mockResolveDeploymentEnvVar.mockReset();
mockResolveDeploymentEnvVar.mockImplementation(async (name: string) =>
- name === 'GITEA_WEBHOOK_SECRET' ? 'gitea-secret' : null,
+ name === 'R_GITEA_WEBHOOK_SECRET' ? 'gitea-secret' : null,
);
mockHandleGiteaPullRequest.mockResolvedValue({ status: 'ok' });
mockHandleGiteaComment.mockResolvedValue({ status: 'ok' });
diff --git a/apps/api/src/handlers/gitea/index.ts b/apps/api/src/handlers/gitea/index.ts
index 0d64cbdb..6218795c 100644
--- a/apps/api/src/handlers/gitea/index.ts
+++ b/apps/api/src/handlers/gitea/index.ts
@@ -46,7 +46,7 @@ gitea.post('/', async (c) => {
try {
const headers = c.req.header();
const body = await c.req.text();
- const secretToken = await resolveDeploymentEnvVar('GITEA_WEBHOOK_SECRET');
+ const secretToken = await resolveDeploymentEnvVar('R_GITEA_WEBHOOK_SECRET');
const verified = verifyGiteaWebhook({
body,
headers,
diff --git a/apps/api/src/handlers/github/__tests__/handlePushConflictCheck.test.ts b/apps/api/src/handlers/github/__tests__/handlePushConflictCheck.test.ts
index 7409c84f..ab089288 100644
--- a/apps/api/src/handlers/github/__tests__/handlePushConflictCheck.test.ts
+++ b/apps/api/src/handlers/github/__tests__/handlePushConflictCheck.test.ts
@@ -82,7 +82,7 @@ describe('handlePushConflictCheck', () => {
mockGetInstallationOctokit.mockResolvedValue({ rest: {} });
});
- it('skips repos in GITHUB_AUTOMATED_SKIP_REPOS before scanning push conflicts', async () => {
+ it('skips repos in R_GITHUB_AUTOMATED_SKIP_REPOS before scanning push conflicts', async () => {
mockIsRepoSkipped.mockReturnValue(true);
const result = await handlePushConflictCheck(createPayload());
diff --git a/apps/api/src/handlers/gitlab/__tests__/index.test.ts b/apps/api/src/handlers/gitlab/__tests__/index.test.ts
index a5b98234..44be9eba 100644
--- a/apps/api/src/handlers/gitlab/__tests__/index.test.ts
+++ b/apps/api/src/handlers/gitlab/__tests__/index.test.ts
@@ -47,7 +47,7 @@ describe('gitlab webhook router', () => {
// Secrets resolve through encrypted deployment env vars, matching
// values saved by /setup rather than the process environment.
mockResolveDeploymentEnvVar.mockImplementation(async (name: string) =>
- name === 'GITLAB_WEBHOOK_SECRET' ? 'gitlab-secret' : null,
+ name === 'R_GITLAB_WEBHOOK_SECRET' ? 'gitlab-secret' : null,
);
mockHandleGitLabMergeRequest.mockResolvedValue({ status: 'ok' });
mockHandleGitLabNote.mockResolvedValue({ status: 'ok' });
diff --git a/apps/api/src/handlers/gitlab/index.ts b/apps/api/src/handlers/gitlab/index.ts
index b5024363..0f310861 100644
--- a/apps/api/src/handlers/gitlab/index.ts
+++ b/apps/api/src/handlers/gitlab/index.ts
@@ -39,8 +39,8 @@ gitlab.post('/', async (c) => {
// encrypted environment_variables by /setup or Settings verify without
// also being copied into the process environment.
const [secretToken, signingToken] = await Promise.all([
- resolveDeploymentEnvVar('GITLAB_WEBHOOK_SECRET'),
- resolveDeploymentEnvVar('GITLAB_WEBHOOK_SIGNING_TOKEN'),
+ resolveDeploymentEnvVar('R_GITLAB_WEBHOOK_SECRET'),
+ resolveDeploymentEnvVar('R_GITLAB_WEBHOOK_SIGNING_TOKEN'),
]);
const verified = verifyGitLabWebhook({
diff --git a/apps/api/src/handlers/health/__tests__/api.test.ts b/apps/api/src/handlers/health/__tests__/api.test.ts
index 54836fe0..1e977e99 100644
--- a/apps/api/src/handlers/health/__tests__/api.test.ts
+++ b/apps/api/src/handlers/health/__tests__/api.test.ts
@@ -8,7 +8,7 @@ const envMock = vi.hoisted(() => ({
| 'preview'
| 'production'
| undefined,
- API_SLOW_REQUEST_THRESHOLD_MS: 3_000,
+ R_API_SLOW_REQUEST_THRESHOLD_MS: 3_000,
}));
const dbExecuteMock = vi.hoisted(() => vi.fn());
diff --git a/apps/api/src/handlers/health/__tests__/controller.test.ts b/apps/api/src/handlers/health/__tests__/controller.test.ts
index f4df1cfc..f3921707 100644
--- a/apps/api/src/handlers/health/__tests__/controller.test.ts
+++ b/apps/api/src/handlers/health/__tests__/controller.test.ts
@@ -8,7 +8,7 @@ const envMock = vi.hoisted(() => ({
| 'preview'
| 'production'
| undefined,
- API_SLOW_REQUEST_THRESHOLD_MS: 3_000,
+ R_API_SLOW_REQUEST_THRESHOLD_MS: 3_000,
}));
const redisGetMock = vi.hoisted(() => vi.fn());
diff --git a/apps/api/src/handlers/health/api.ts b/apps/api/src/handlers/health/api.ts
index e024cb52..b4235724 100644
--- a/apps/api/src/handlers/health/api.ts
+++ b/apps/api/src/handlers/health/api.ts
@@ -20,7 +20,7 @@ export const apiHealth = new Hono<{ Variables: Variables }>();
apiHealth.get('/', async (c) => {
const requestStartedAt = Date.now();
- const slowThresholdMs = Env.API_SLOW_REQUEST_THRESHOLD_MS;
+ const slowThresholdMs = Env.R_API_SLOW_REQUEST_THRESHOLD_MS;
const dbCheck = await runTimedHealthCheck('db', async () => {
try {
diff --git a/apps/api/src/handlers/health/controller.ts b/apps/api/src/handlers/health/controller.ts
index a6db815d..d34f2bb4 100644
--- a/apps/api/src/handlers/health/controller.ts
+++ b/apps/api/src/handlers/health/controller.ts
@@ -170,7 +170,7 @@ async function checkController(): Promise {
controllerHealth.get('/', async (c) => {
const requestStartedAt = Date.now();
- const slowThresholdMs = Env.API_SLOW_REQUEST_THRESHOLD_MS;
+ const slowThresholdMs = Env.R_API_SLOW_REQUEST_THRESHOLD_MS;
const checks = await Promise.all([
runTimedHealthCheck('controllerHeartbeat', checkController),
diff --git a/apps/api/src/handlers/linear/__tests__/linear-active-run-priority.test.ts b/apps/api/src/handlers/linear/__tests__/linear-active-run-priority.test.ts
index 1ba7ae63..3e1253c3 100644
--- a/apps/api/src/handlers/linear/__tests__/linear-active-run-priority.test.ts
+++ b/apps/api/src/handlers/linear/__tests__/linear-active-run-priority.test.ts
@@ -43,7 +43,7 @@ vi.mock('@roomote/env', async (importOriginal) => {
Env: {
R_LINEAR_WEBHOOK_SECRET: 'test-linear-secret',
R_APP_URL: 'https://app.roomote.example',
- PREVIEW_PROXY_BASE_URL: 'https://preview.roomote.example',
+ R_PREVIEW_PROXY_BASE_URL: 'https://preview.roomote.example',
},
};
});
diff --git a/apps/api/src/handlers/linear/__tests__/linear-routing-confirmation.test.ts b/apps/api/src/handlers/linear/__tests__/linear-routing-confirmation.test.ts
index 8fe16f46..0d68b244 100644
--- a/apps/api/src/handlers/linear/__tests__/linear-routing-confirmation.test.ts
+++ b/apps/api/src/handlers/linear/__tests__/linear-routing-confirmation.test.ts
@@ -41,8 +41,8 @@ vi.mock('@roomote/env', async (importOriginal) => {
Env: {
R_LINEAR_WEBHOOK_SECRET: 'test-linear-secret',
R_APP_URL: 'https://app.roomote.example',
- PREVIEW_PROXY_BASE_URL: 'https://preview.roomote.example',
- TRPC_URL: 'https://api.roomote.example',
+ R_PREVIEW_PROXY_BASE_URL: 'https://preview.roomote.example',
+ R_TRPC_URL: 'https://api.roomote.example',
},
};
});
diff --git a/apps/api/src/handlers/linear/index.ts b/apps/api/src/handlers/linear/index.ts
index a58a7490..8f264785 100644
--- a/apps/api/src/handlers/linear/index.ts
+++ b/apps/api/src/handlers/linear/index.ts
@@ -928,7 +928,7 @@ async function handleAgentSessionEvent(
body: c.body,
username: c.user?.name,
})),
- apiBaseUrl: Env.TRPC_URL ?? Env.R_APP_URL,
+ apiBaseUrl: Env.R_TRPC_URL ?? Env.R_APP_URL,
});
// Attempt LLM routing
diff --git a/apps/api/src/handlers/mcp/github.ts b/apps/api/src/handlers/mcp/github.ts
index b6a422b3..d517164d 100644
--- a/apps/api/src/handlers/mcp/github.ts
+++ b/apps/api/src/handlers/mcp/github.ts
@@ -36,7 +36,7 @@ export function createGithubMcp(options?: {
}) {
return createMcpProxy({
name: 'GitHub',
- upstream: Env.GITHUB_MCP_SERVER_URL ?? DEFAULT_GITHUB_MCP_URL,
+ upstream: Env.R_GITHUB_MCP_SERVER_URL ?? DEFAULT_GITHUB_MCP_URL,
allowAuthTokens: options?.allowAuthTokens,
allowedToolNames: options?.allowedToolNames,
resolveCredentials: async () => {
diff --git a/apps/api/src/handlers/slack/constants.ts b/apps/api/src/handlers/slack/constants.ts
index ea382444..5b2b3f28 100644
--- a/apps/api/src/handlers/slack/constants.ts
+++ b/apps/api/src/handlers/slack/constants.ts
@@ -1,7 +1,7 @@
import { Env } from '@roomote/env';
const UNFURL_ALLOWED_DOMAIN_SUFFIXES = new Set(
- (Env.SLACK_UNFURL_ALLOWED_DOMAINS ?? new URL(Env.R_APP_URL).hostname)
+ (Env.R_SLACK_UNFURL_ALLOWED_DOMAINS ?? new URL(Env.R_APP_URL).hostname)
.split(',')
.map((domain) => domain.trim())
.filter((domain) => domain.length > 0),
diff --git a/apps/api/src/handlers/slack/events/channel-auto-start-unlinked.test.ts b/apps/api/src/handlers/slack/events/channel-auto-start-unlinked.test.ts
index e1df2591..00b59a0b 100644
--- a/apps/api/src/handlers/slack/events/channel-auto-start-unlinked.test.ts
+++ b/apps/api/src/handlers/slack/events/channel-auto-start-unlinked.test.ts
@@ -11,7 +11,7 @@ const { enrichSlackMessageEventMock, isRoomoteAuthoredSlackEventMock } =
}));
vi.mock('@roomote/env', () => ({
- Env: { TRPC_URL: null, R_APP_URL: 'http://localhost:3000' },
+ Env: { R_TRPC_URL: null, R_APP_URL: 'http://localhost:3000' },
}));
vi.mock('@roomote/cloud-agents/server', () => ({
diff --git a/apps/api/src/handlers/slack/events/message-entry-unmentioned-routing.test.ts b/apps/api/src/handlers/slack/events/message-entry-unmentioned-routing.test.ts
index 6221014b..d913e426 100644
--- a/apps/api/src/handlers/slack/events/message-entry-unmentioned-routing.test.ts
+++ b/apps/api/src/handlers/slack/events/message-entry-unmentioned-routing.test.ts
@@ -17,7 +17,7 @@ const {
}));
vi.mock('@roomote/env', () => ({
- Env: { TRPC_URL: null, R_APP_URL: 'http://localhost:3000' },
+ Env: { R_TRPC_URL: null, R_APP_URL: 'http://localhost:3000' },
}));
vi.mock('@roomote/cloud-agents/server', () => ({
diff --git a/apps/api/src/handlers/slack/events/message-entry.ts b/apps/api/src/handlers/slack/events/message-entry.ts
index 69053d83..080419e1 100644
--- a/apps/api/src/handlers/slack/events/message-entry.ts
+++ b/apps/api/src/handlers/slack/events/message-entry.ts
@@ -1556,7 +1556,7 @@ function startFastAgentResponse(params: {
processFastAgentMessage({
...fastAgentParams,
- apiBaseUrl: Env.TRPC_URL ?? Env.R_APP_URL,
+ apiBaseUrl: Env.R_TRPC_URL ?? Env.R_APP_URL,
}).catch((error) => {
console.error(
errorLogPrefix,
diff --git a/apps/api/src/handlers/teams/__tests__/index.test.ts b/apps/api/src/handlers/teams/__tests__/index.test.ts
index 1f4a0662..26791102 100644
--- a/apps/api/src/handlers/teams/__tests__/index.test.ts
+++ b/apps/api/src/handlers/teams/__tests__/index.test.ts
@@ -49,7 +49,7 @@ const {
R_MICROSOFT_CLIENT_ID: 'microsoft-client-id' as string | undefined,
R_MICROSOFT_CLIENT_SECRET: 'microsoft-client-secret' as string | undefined,
R_MICROSOFT_TENANT_ID: 'microsoft-tenant-id' as string | undefined,
- TRPC_URL: 'https://api.example.com',
+ R_TRPC_URL: 'https://api.example.com',
},
fetchMessageImageDataUrlsMock: vi.fn(),
findFirstMock: vi.fn(),
diff --git a/apps/api/src/handlers/teams/index.ts b/apps/api/src/handlers/teams/index.ts
index 7bbf6ebb..66e8112e 100644
--- a/apps/api/src/handlers/teams/index.ts
+++ b/apps/api/src/handlers/teams/index.ts
@@ -1253,7 +1253,7 @@ async function startNewTeamsTask(input: {
...(input.queuedMessage.images?.length
? { images: input.queuedMessage.images }
: {}),
- apiBaseUrl: Env.TRPC_URL ?? Env.R_APP_URL,
+ apiBaseUrl: Env.R_TRPC_URL ?? Env.R_APP_URL,
});
const routingDecision = await routeTask(routingContext);
diff --git a/apps/api/src/handlers/telegram/__tests__/index.test.ts b/apps/api/src/handlers/telegram/__tests__/index.test.ts
index 58b61909..918876d6 100644
--- a/apps/api/src/handlers/telegram/__tests__/index.test.ts
+++ b/apps/api/src/handlers/telegram/__tests__/index.test.ts
@@ -56,7 +56,7 @@ const {
R_APP_URL: 'https://app.example.com',
R_TELEGRAM_BOT_TOKEN: 'bot-token' as string | undefined,
R_TELEGRAM_WEBHOOK_SECRET: 'secret' as string | undefined,
- TRPC_URL: 'https://api.example.com' as string | undefined,
+ R_TRPC_URL: 'https://api.example.com' as string | undefined,
},
getTaskUrlMock: vi.fn(() => 'https://app.example.com/task/task-new'),
insertMock: vi.fn(),
@@ -348,7 +348,7 @@ describe('Telegram webhook handler', () => {
envMock.R_APP_URL = 'https://app.example.com';
envMock.R_TELEGRAM_BOT_TOKEN = 'bot-token';
envMock.R_TELEGRAM_WEBHOOK_SECRET = 'secret';
- envMock.TRPC_URL = 'https://api.example.com';
+ envMock.R_TRPC_URL = 'https://api.example.com';
redisSetMock.mockResolvedValue('OK');
redisDelMock.mockResolvedValue(1);
redisGetMock.mockResolvedValue(null);
diff --git a/apps/api/src/handlers/telegram/task-orchestration.ts b/apps/api/src/handlers/telegram/task-orchestration.ts
index 8970f00c..8b3526bd 100644
--- a/apps/api/src/handlers/telegram/task-orchestration.ts
+++ b/apps/api/src/handlers/telegram/task-orchestration.ts
@@ -160,7 +160,7 @@ export async function startNewTelegramTask(input: {
...(input.queuedMessage.images?.length
? { images: input.queuedMessage.images }
: {}),
- apiBaseUrl: Env.TRPC_URL ?? Env.R_APP_URL,
+ apiBaseUrl: Env.R_TRPC_URL ?? Env.R_APP_URL,
});
const routingDecision = await routeTask(routingContext);
diff --git a/apps/api/src/logging.ts b/apps/api/src/logging.ts
index 4638b791..28ca82bf 100644
--- a/apps/api/src/logging.ts
+++ b/apps/api/src/logging.ts
@@ -6,8 +6,8 @@ function isApiDebugLoggingEnabled(): boolean {
return (
effectiveAppEnv !== 'production' ||
- Env.API_DEBUG_LOGS === '1' ||
- Env.API_DEBUG_LOGS === 'true'
+ Env.R_API_DEBUG_LOGS === '1' ||
+ Env.R_API_DEBUG_LOGS === 'true'
);
}
diff --git a/apps/api/src/middleware/__tests__/requestObservabilityMiddleware.test.ts b/apps/api/src/middleware/__tests__/requestObservabilityMiddleware.test.ts
index 2c2b21cb..a93d13ed 100644
--- a/apps/api/src/middleware/__tests__/requestObservabilityMiddleware.test.ts
+++ b/apps/api/src/middleware/__tests__/requestObservabilityMiddleware.test.ts
@@ -7,7 +7,7 @@ vi.mock('@roomote/env', async (importOriginal) => {
return {
...actual,
Env: {
- API_SLOW_REQUEST_THRESHOLD_MS: 5_000,
+ R_API_SLOW_REQUEST_THRESHOLD_MS: 5_000,
},
};
});
@@ -63,7 +63,7 @@ describe('requestObservabilityMiddleware', () => {
app.use('*', requestObservabilityMiddleware);
app.get('/slow', (c) => {
- now += Env.API_SLOW_REQUEST_THRESHOLD_MS;
+ now += Env.R_API_SLOW_REQUEST_THRESHOLD_MS;
return c.text('ok');
});
@@ -74,7 +74,7 @@ describe('requestObservabilityMiddleware', () => {
});
expect(warnSpy).toHaveBeenCalledWith(
- `[API Slow Request] GET /slow status=200 durationMs=${Env.API_SLOW_REQUEST_THRESHOLD_MS} requestId=req_123`,
+ `[API Slow Request] GET /slow status=200 durationMs=${Env.R_API_SLOW_REQUEST_THRESHOLD_MS} requestId=req_123`,
);
dateNowSpy.mockRestore();
diff --git a/apps/api/src/middleware/requestObservabilityMiddleware.ts b/apps/api/src/middleware/requestObservabilityMiddleware.ts
index f8c42b5a..c959a81f 100644
--- a/apps/api/src/middleware/requestObservabilityMiddleware.ts
+++ b/apps/api/src/middleware/requestObservabilityMiddleware.ts
@@ -47,7 +47,7 @@ export const requestObservabilityMiddleware: MiddlewareHandler = async (
c,
next,
) => {
- const slowRequestThresholdMs = Env.API_SLOW_REQUEST_THRESHOLD_MS;
+ const slowRequestThresholdMs = Env.R_API_SLOW_REQUEST_THRESHOLD_MS;
const startedAt = Date.now();
let thrownError: unknown;
diff --git a/apps/api/src/server.ts b/apps/api/src/server.ts
index 32c41424..75ab9a2b 100644
--- a/apps/api/src/server.ts
+++ b/apps/api/src/server.ts
@@ -101,16 +101,16 @@ function observedFetchImpl(
export function installApiObservedFetch(): void {
installGlobalObservedFetch({
serviceName: 'api',
- slowRequestThresholdMs: Env.API_SLOW_EXTERNAL_REQUEST_THRESHOLD_MS,
+ slowRequestThresholdMs: Env.R_API_SLOW_EXTERNAL_REQUEST_THRESHOLD_MS,
fetchImpl: observedFetchImpl,
log: createSingleLineWarnLogger(),
internalHosts: buildInternalRequestHosts([
Env.R_APP_URL,
- Env.TRPC_URL,
- Env.PREVIEW_PROXY_BASE_URL,
+ Env.R_TRPC_URL,
+ Env.R_PREVIEW_PROXY_BASE_URL,
]),
internalDomainSuffixes: parseInternalRequestDomainSuffixes(
- Env.PREVIEW_DOMAINS,
+ Env.R_PREVIEW_DOMAINS,
),
});
}
diff --git a/apps/bullmq/src/scheduled-jobs/__tests__/webhook-cleanup.test.ts b/apps/bullmq/src/scheduled-jobs/__tests__/webhook-cleanup.test.ts
index 1503470f..f7ac3477 100644
--- a/apps/bullmq/src/scheduled-jobs/__tests__/webhook-cleanup.test.ts
+++ b/apps/bullmq/src/scheduled-jobs/__tests__/webhook-cleanup.test.ts
@@ -9,7 +9,7 @@ vi.mock('@roomote/db/server', () => ({
}));
vi.mock('@roomote/env', () => ({
- Env: { WEBHOOK_RETENTION_DAYS: 30 },
+ Env: { R_WEBHOOK_RETENTION_DAYS: 30 },
}));
import { webhookCleanupJob } from '../webhook-cleanup';
@@ -22,7 +22,7 @@ describe('webhookCleanupJob', () => {
mockDeleteExpiredWebhooks.mockResolvedValue(0);
});
- it('deletes rows older than WEBHOOK_RETENTION_DAYS', async () => {
+ it('deletes rows older than R_WEBHOOK_RETENTION_DAYS', async () => {
const before = Date.now();
await webhookCleanupJob();
const after = Date.now();
diff --git a/apps/bullmq/src/scheduled-jobs/standby-retention.test.ts b/apps/bullmq/src/scheduled-jobs/standby-retention.test.ts
index c5894eb6..52164307 100644
--- a/apps/bullmq/src/scheduled-jobs/standby-retention.test.ts
+++ b/apps/bullmq/src/scheduled-jobs/standby-retention.test.ts
@@ -61,8 +61,8 @@ describe('resolveStandbyRetentionPolicy', () => {
it('applies saved or runtime provider overrides', () => {
expect(
resolveStandbyRetentionPolicy('docker', {
- DOCKER_STANDBY_MAX_COUNT: '3',
- DOCKER_STANDBY_MAX_AGE_HOURS: '12',
+ R_DOCKER_STANDBY_MAX_COUNT: '3',
+ R_DOCKER_STANDBY_MAX_AGE_HOURS: '12',
}),
).toEqual({ maxCount: 3, maxAgeMs: 12 * 60 * 60 * 1_000 });
});
@@ -70,8 +70,8 @@ describe('resolveStandbyRetentionPolicy', () => {
it('falls back safely for invalid values', () => {
expect(
resolveStandbyRetentionPolicy('blaxel', {
- BLAXEL_STANDBY_MAX_COUNT: '-1',
- BLAXEL_STANDBY_MAX_AGE_HOURS: '169',
+ R_BLAXEL_STANDBY_MAX_COUNT: '-1',
+ R_BLAXEL_STANDBY_MAX_AGE_HOURS: '169',
}),
).toEqual({ maxCount: 25, maxAgeMs: 168 * 60 * 60 * 1_000 });
});
diff --git a/apps/bullmq/src/scheduled-jobs/webhook-cleanup.ts b/apps/bullmq/src/scheduled-jobs/webhook-cleanup.ts
index 4da90a8a..74fd1515 100644
--- a/apps/bullmq/src/scheduled-jobs/webhook-cleanup.ts
+++ b/apps/bullmq/src/scheduled-jobs/webhook-cleanup.ts
@@ -8,12 +8,12 @@ const MS_PER_DAY = 24 * 60 * 60 * 1000;
/**
* Daily retention job for the `webhooks` audit table. Recorded webhook
* payloads are only needed for short-term debugging and idempotency, so rows
- * older than WEBHOOK_RETENTION_DAYS (default 30) are deleted. Payload
+ * older than R_WEBHOOK_RETENTION_DAYS (default 30) are deleted. Payload
* redaction at write time (apps/api webhook-payload-redaction) covers the
* window while rows are retained.
*/
export async function webhookCleanupJob(): Promise {
- const retentionDays = Env.WEBHOOK_RETENTION_DAYS;
+ const retentionDays = Env.R_WEBHOOK_RETENTION_DAYS;
const olderThan = new Date(Date.now() - retentionDays * MS_PER_DAY);
const deleted = await deleteExpiredWebhooks(db, { olderThan });
diff --git a/apps/controller/src/BaseController.ts b/apps/controller/src/BaseController.ts
index fc642cd5..cd0858c5 100644
--- a/apps/controller/src/BaseController.ts
+++ b/apps/controller/src/BaseController.ts
@@ -70,7 +70,7 @@ export abstract class BaseController {
protected readonly appEnv: 'development' | 'preview' | 'production',
) {
const useLocalReleaseArtifacts = this.appEnv === 'development';
- const configuredWorkerReleasePath = Env.DOCKER_WORKER_RELEASE_PATH;
+ const configuredWorkerReleasePath = Env.R_DOCKER_WORKER_RELEASE_PATH;
this.localWorkerReleasePath =
configuredWorkerReleasePath ??
diff --git a/apps/controller/src/RoomoteController.ts b/apps/controller/src/RoomoteController.ts
index af5972d6..829748c9 100644
--- a/apps/controller/src/RoomoteController.ts
+++ b/apps/controller/src/RoomoteController.ts
@@ -112,18 +112,18 @@ export class RoomoteController extends BaseController {
}
case 'docker': {
await spawnDockerWorker(taskRun, authToken, {
- image: Env.DOCKER_WORKER_IMAGE,
- platform: Env.DOCKER_WORKER_PLATFORM,
- network: Env.DOCKER_WORKER_NETWORK,
+ image: Env.R_DOCKER_WORKER_IMAGE,
+ platform: Env.R_DOCKER_WORKER_PLATFORM,
+ network: Env.R_DOCKER_WORKER_NETWORK,
dockerTimeoutMs: timeoutMs,
- cpuLimit: Env.DOCKER_WORKER_CPU_LIMIT,
- memoryLimit: Env.DOCKER_WORKER_MEMORY_LIMIT,
- pidsLimit: Env.DOCKER_WORKER_PIDS_LIMIT,
- diskLimit: Env.DOCKER_WORKER_DISK_LIMIT,
- allowUnboundedDisk: Env.DOCKER_WORKER_ALLOW_UNBOUNDED_DISK,
- logMaxSize: Env.DOCKER_WORKER_LOG_MAX_SIZE,
- logMaxFiles: Env.DOCKER_WORKER_LOG_MAX_FILES,
- egressPolicy: Env.DOCKER_WORKER_EGRESS_POLICY,
+ cpuLimit: Env.R_DOCKER_WORKER_CPU_LIMIT,
+ memoryLimit: Env.R_DOCKER_WORKER_MEMORY_LIMIT,
+ pidsLimit: Env.R_DOCKER_WORKER_PIDS_LIMIT,
+ diskLimit: Env.R_DOCKER_WORKER_DISK_LIMIT,
+ allowUnboundedDisk: Env.R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK,
+ logMaxSize: Env.R_DOCKER_WORKER_LOG_MAX_SIZE,
+ logMaxFiles: Env.R_DOCKER_WORKER_LOG_MAX_FILES,
+ egressPolicy: Env.R_DOCKER_WORKER_EGRESS_POLICY,
localWorkerReleasePath: this.localWorkerReleasePath,
deploymentSlug: deploymentSlug,
});
@@ -223,7 +223,7 @@ export class RoomoteController extends BaseController {
private async cleanStaleDockerSandboxes(): Promise {
try {
await cleanupStaleDockerSandboxes({
- controlNetwork: Env.DOCKER_WORKER_NETWORK,
+ controlNetwork: Env.R_DOCKER_WORKER_NETWORK,
});
} catch (error) {
console.error(
diff --git a/apps/controller/src/__tests__/RoomoteController.test.ts b/apps/controller/src/__tests__/RoomoteController.test.ts
index 7da775d6..3a81a4ba 100644
--- a/apps/controller/src/__tests__/RoomoteController.test.ts
+++ b/apps/controller/src/__tests__/RoomoteController.test.ts
@@ -21,19 +21,19 @@ const {
MODAL_ECR_OIDC_ROLE_ARN: undefined,
MODAL_ECR_REGION: undefined,
MODAL_REGIONS: undefined,
- TRPC_URL: 'http://localhost:13001',
- DOCKER_WORKER_IMAGE: 'roomote-worker:local',
- DOCKER_WORKER_PLATFORM: 'linux/amd64',
- DOCKER_WORKER_NETWORK: undefined,
- DOCKER_WORKER_RELEASE_PATH: undefined,
- DOCKER_WORKER_CPU_LIMIT: 2,
- DOCKER_WORKER_MEMORY_LIMIT: '4g',
- DOCKER_WORKER_PIDS_LIMIT: 512,
- DOCKER_WORKER_DISK_LIMIT: '20g',
- DOCKER_WORKER_ALLOW_UNBOUNDED_DISK: false,
- DOCKER_WORKER_LOG_MAX_SIZE: '10m',
- DOCKER_WORKER_LOG_MAX_FILES: 3,
- DOCKER_WORKER_EGRESS_POLICY: 'internet',
+ R_TRPC_URL: 'http://localhost:13001',
+ R_DOCKER_WORKER_IMAGE: 'roomote-worker:local',
+ R_DOCKER_WORKER_PLATFORM: 'linux/amd64',
+ R_DOCKER_WORKER_NETWORK: undefined,
+ R_DOCKER_WORKER_RELEASE_PATH: undefined,
+ R_DOCKER_WORKER_CPU_LIMIT: 2,
+ R_DOCKER_WORKER_MEMORY_LIMIT: '4g',
+ R_DOCKER_WORKER_PIDS_LIMIT: 512,
+ R_DOCKER_WORKER_DISK_LIMIT: '20g',
+ R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK: false,
+ R_DOCKER_WORKER_LOG_MAX_SIZE: '10m',
+ R_DOCKER_WORKER_LOG_MAX_FILES: 3,
+ R_DOCKER_WORKER_EGRESS_POLICY: 'internet',
DAYTONA_API_KEY: 'daytona-key',
DAYTONA_API_URL: undefined,
DAYTONA_TARGET: undefined,
@@ -114,19 +114,19 @@ describe('RoomoteController', () => {
mockEnv.MODAL_ECR_OIDC_ROLE_ARN = undefined;
mockEnv.MODAL_ECR_REGION = undefined;
mockEnv.MODAL_REGIONS = undefined;
- mockEnv.TRPC_URL = 'http://localhost:13001';
- mockEnv.DOCKER_WORKER_IMAGE = 'roomote-worker:local';
- mockEnv.DOCKER_WORKER_PLATFORM = 'linux/amd64';
- mockEnv.DOCKER_WORKER_NETWORK = undefined;
- mockEnv.DOCKER_WORKER_RELEASE_PATH = undefined;
- mockEnv.DOCKER_WORKER_CPU_LIMIT = 2;
- mockEnv.DOCKER_WORKER_MEMORY_LIMIT = '4g';
- mockEnv.DOCKER_WORKER_PIDS_LIMIT = 512;
- mockEnv.DOCKER_WORKER_DISK_LIMIT = '20g';
- mockEnv.DOCKER_WORKER_ALLOW_UNBOUNDED_DISK = false;
- mockEnv.DOCKER_WORKER_LOG_MAX_SIZE = '10m';
- mockEnv.DOCKER_WORKER_LOG_MAX_FILES = 3;
- mockEnv.DOCKER_WORKER_EGRESS_POLICY = 'internet';
+ mockEnv.R_TRPC_URL = 'http://localhost:13001';
+ mockEnv.R_DOCKER_WORKER_IMAGE = 'roomote-worker:local';
+ mockEnv.R_DOCKER_WORKER_PLATFORM = 'linux/amd64';
+ mockEnv.R_DOCKER_WORKER_NETWORK = undefined;
+ mockEnv.R_DOCKER_WORKER_RELEASE_PATH = undefined;
+ mockEnv.R_DOCKER_WORKER_CPU_LIMIT = 2;
+ mockEnv.R_DOCKER_WORKER_MEMORY_LIMIT = '4g';
+ mockEnv.R_DOCKER_WORKER_PIDS_LIMIT = 512;
+ mockEnv.R_DOCKER_WORKER_DISK_LIMIT = '20g';
+ mockEnv.R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK = false;
+ mockEnv.R_DOCKER_WORKER_LOG_MAX_SIZE = '10m';
+ mockEnv.R_DOCKER_WORKER_LOG_MAX_FILES = 3;
+ mockEnv.R_DOCKER_WORKER_EGRESS_POLICY = 'internet';
mockEnv.DAYTONA_API_KEY = 'daytona-key';
mockEnv.DAYTONA_API_URL = undefined;
mockEnv.DAYTONA_TARGET = undefined;
@@ -149,7 +149,7 @@ describe('RoomoteController', () => {
});
it('spawns Docker workers when Docker is the selected provider', async () => {
- mockEnv.DOCKER_WORKER_NETWORK = 'roomote_default';
+ mockEnv.R_DOCKER_WORKER_NETWORK = 'roomote_default';
const controller = new RoomoteController('production');
await (
diff --git a/apps/controller/src/__tests__/utils.test.ts b/apps/controller/src/__tests__/utils.test.ts
index 467caea6..a5607765 100644
--- a/apps/controller/src/__tests__/utils.test.ts
+++ b/apps/controller/src/__tests__/utils.test.ts
@@ -12,10 +12,10 @@ import {
updateTaskRunMachine,
} from '../utils';
-const originalTrpcUrl = process.env.TRPC_URL;
+const originalTrpcUrl = process.env.R_TRPC_URL;
const originalRoomoteAppUrl = process.env.R_APP_URL;
-const originalPreviewProxyBaseUrl = process.env.PREVIEW_PROXY_BASE_URL;
-const originalPreviewDomains = process.env.PREVIEW_DOMAINS;
+const originalPreviewProxyBaseUrl = process.env.R_PREVIEW_PROXY_BASE_URL;
+const originalPreviewDomains = process.env.R_PREVIEW_DOMAINS;
const originalRoomotePreviewDomain = process.env.ROOMOTE_PREVIEW_DOMAIN;
const mockUpdate = vi.fn().mockReturnValue({
@@ -89,18 +89,18 @@ describe('getNamedPortsForTaskRun', () => {
isReady: true,
},
});
- delete process.env.TRPC_URL;
+ delete process.env.R_TRPC_URL;
delete process.env.R_APP_URL;
- process.env.PREVIEW_PROXY_BASE_URL = 'https://preview.roomote.test';
- process.env.PREVIEW_DOMAINS = 'preview.roomote.test';
+ process.env.R_PREVIEW_PROXY_BASE_URL = 'https://preview.roomote.test';
+ process.env.R_PREVIEW_DOMAINS = 'preview.roomote.test';
delete process.env.ROOMOTE_PREVIEW_DOMAIN;
});
afterAll(() => {
if (originalTrpcUrl === undefined) {
- delete process.env.TRPC_URL;
+ delete process.env.R_TRPC_URL;
} else {
- process.env.TRPC_URL = originalTrpcUrl;
+ process.env.R_TRPC_URL = originalTrpcUrl;
}
if (originalRoomoteAppUrl === undefined) {
@@ -110,15 +110,15 @@ describe('getNamedPortsForTaskRun', () => {
}
if (originalPreviewProxyBaseUrl === undefined) {
- delete process.env.PREVIEW_PROXY_BASE_URL;
+ delete process.env.R_PREVIEW_PROXY_BASE_URL;
} else {
- process.env.PREVIEW_PROXY_BASE_URL = originalPreviewProxyBaseUrl;
+ process.env.R_PREVIEW_PROXY_BASE_URL = originalPreviewProxyBaseUrl;
}
if (originalPreviewDomains === undefined) {
- delete process.env.PREVIEW_DOMAINS;
+ delete process.env.R_PREVIEW_DOMAINS;
} else {
- process.env.PREVIEW_DOMAINS = originalPreviewDomains;
+ process.env.R_PREVIEW_DOMAINS = originalPreviewDomains;
}
if (originalRoomotePreviewDomain === undefined) {
@@ -243,7 +243,7 @@ describe('getNamedPortsForTaskRun', () => {
});
it('does not expose callback ports from loopback controller URLs', async () => {
- process.env.TRPC_URL = 'http://127.0.0.1:3001';
+ process.env.R_TRPC_URL = 'http://127.0.0.1:3001';
process.env.R_APP_URL = 'http://localhost:3000';
vi.mocked(db.query.environments.findFirst).mockResolvedValue({
id: 'env-123',
diff --git a/apps/controller/src/compute-providers/__tests__/spawn-modal-worker.test.ts b/apps/controller/src/compute-providers/__tests__/spawn-modal-worker.test.ts
index ddb77db0..6707feb1 100644
--- a/apps/controller/src/compute-providers/__tests__/spawn-modal-worker.test.ts
+++ b/apps/controller/src/compute-providers/__tests__/spawn-modal-worker.test.ts
@@ -87,7 +87,7 @@ describe('spawnModalWorker', () => {
beforeEach(() => {
vi.clearAllMocks();
mockShouldEnableAuthBypassForTaskRun.mockReturnValue(true);
- delete process.env.PREVIEW_PROXY_BASE_URL;
+ delete process.env.R_PREVIEW_PROXY_BASE_URL;
mockCreateModalMachine.mockResolvedValue({
machineId: 'modal-machine-123',
diff --git a/apps/controller/src/compute-providers/spawn-docker-worker.ts b/apps/controller/src/compute-providers/spawn-docker-worker.ts
index 40795860..8bc188a4 100644
--- a/apps/controller/src/compute-providers/spawn-docker-worker.ts
+++ b/apps/controller/src/compute-providers/spawn-docker-worker.ts
@@ -122,8 +122,8 @@ export async function spawnDockerWorker(
const resolvedPreviewRuntimeConfig =
await resolveEffectivePreviewRuntimeConfig({
runtimeEnv: process.env,
- defaultPreviewProxyBaseUrl: Env.PREVIEW_PROXY_BASE_URL,
- defaultPreviewDomains: Env.PREVIEW_DOMAINS,
+ defaultPreviewProxyBaseUrl: Env.R_PREVIEW_PROXY_BASE_URL,
+ defaultPreviewDomains: Env.R_PREVIEW_DOMAINS,
});
const containerName = isStandbyResume
@@ -220,7 +220,7 @@ export async function spawnDockerWorker(
}
console.warn(
- `[spawnDockerWorker] Docker storage driver cannot enforce writable-layer limit ${config.diskLimit}; DOCKER_WORKER_ALLOW_UNBOUNDED_DISK is enabled, so this task will continue without --storage-opt size.`,
+ `[spawnDockerWorker] Docker storage driver cannot enforce writable-layer limit ${config.diskLimit}; R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK is enabled, so this task will continue without --storage-opt size.`,
);
await docker(['rm', '-f', containerName], { allowFailure: true });
containerId = await startContainer();
@@ -308,12 +308,14 @@ export async function spawnDockerWorker(
image: config.image,
extraEnv: {
SANDBOX_TIMEOUT_MS: String(config.dockerTimeoutMs),
- TRPC_URL: toContainerReachableUrl(process.env.TRPC_URL ?? Env.TRPC_URL),
+ R_TRPC_URL: toContainerReachableUrl(
+ process.env.R_TRPC_URL ?? Env.R_TRPC_URL,
+ ),
// Mock-Slack parity: worker-side SlackNotifier calls (question blocks,
// reactions) must reach the same mock harness the API uses.
- ...(process.env.SLACK_API_BASE_URL && {
- SLACK_API_BASE_URL: toContainerReachableUrl(
- process.env.SLACK_API_BASE_URL,
+ ...(process.env.R_SLACK_API_BASE_URL && {
+ R_SLACK_API_BASE_URL: toContainerReachableUrl(
+ process.env.R_SLACK_API_BASE_URL,
),
}),
R_APP_URL: toContainerReachableUrl(
@@ -325,11 +327,11 @@ export async function spawnDockerWorker(
process.env.R_APP_URL ?? Env.R_APP_URL,
),
...(resolvedPreviewRuntimeConfig.effective.previewProxyBaseUrl && {
- PREVIEW_PROXY_BASE_URL:
+ R_PREVIEW_PROXY_BASE_URL:
resolvedPreviewRuntimeConfig.effective.previewProxyBaseUrl,
}),
...(resolvedPreviewRuntimeConfig.effective.previewDomains && {
- PREVIEW_DOMAINS:
+ R_PREVIEW_DOMAINS:
resolvedPreviewRuntimeConfig.effective.previewDomains,
}),
...(resolvedPreviewRuntimeConfig.effective.roomotePreviewDomain && {
@@ -392,7 +394,7 @@ export function shouldRetryDockerWorkerWithoutDiskLimit(params: {
if (!params.allowUnboundedDisk) {
throw new NonRetryableSpawnError(
- `Docker storage driver cannot enforce writable-layer limit ${params.diskLimit}. Refusing to start an unbounded task. Configure a quota-capable Docker data root or set DOCKER_WORKER_ALLOW_UNBOUNDED_DISK=true to explicitly accept the host disk-exhaustion risk.`,
+ `Docker storage driver cannot enforce writable-layer limit ${params.diskLimit}. Refusing to start an unbounded task. Configure a quota-capable Docker data root or set R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK=true to explicitly accept the host disk-exhaustion risk.`,
);
}
diff --git a/apps/controller/src/utils.ts b/apps/controller/src/utils.ts
index af157c1f..43ef3119 100644
--- a/apps/controller/src/utils.ts
+++ b/apps/controller/src/utils.ts
@@ -66,8 +66,8 @@ async function resolveDeploymentRuntimeFlags(): Promise
const metadata = normalizeMetadataRecord(settings?.metadata);
const previewRuntimeConfig = await resolveEffectivePreviewRuntimeConfig({
runtimeEnv: process.env,
- defaultPreviewProxyBaseUrl: Env.PREVIEW_PROXY_BASE_URL,
- defaultPreviewDomains: Env.PREVIEW_DOMAINS,
+ defaultPreviewProxyBaseUrl: Env.R_PREVIEW_PROXY_BASE_URL,
+ defaultPreviewDomains: Env.R_PREVIEW_DOMAINS,
});
return {
diff --git a/apps/dev/src/index.ts b/apps/dev/src/index.ts
index 7ddeea89..23bbbec0 100644
--- a/apps/dev/src/index.ts
+++ b/apps/dev/src/index.ts
@@ -54,10 +54,10 @@ class LocalDevStarter {
options.publicUrl = publicUrlResolution.publicUrl;
process.env.R_PUBLIC_URL = publicUrlResolution.publicUrl;
process.env.R_APP_URL = publicUrlResolution.publicUrl;
- // Match the deployed TRPC_URL contract so hosted compute workers
+ // Match the deployed R_TRPC_URL contract so hosted compute workers
// (Vercel Sandbox, Modal, Daytona, E2B) can reach the API through the
// public app URL instead of an unreachable localhost address.
- process.env.TRPC_URL = `${publicUrlResolution.publicUrl}/_roomote-api`;
+ process.env.R_TRPC_URL = `${publicUrlResolution.publicUrl}/_roomote-api`;
console.info(
`🌐 Public URL: ${publicUrlResolution.publicUrl}${
@@ -83,7 +83,7 @@ class LocalDevStarter {
await WorkerReleaseService.build(options);
}
- if ((process.env.DEFAULT_COMPUTE_PROVIDER ?? 'docker') === 'docker') {
+ if ((process.env.R_DEFAULT_COMPUTE_PROVIDER ?? 'docker') === 'docker') {
await DockerService.ensureWorkerImage(options.verbose);
}
diff --git a/apps/dev/src/services/__tests__/docker.test.ts b/apps/dev/src/services/__tests__/docker.test.ts
index fcead02e..8bd4e3aa 100644
--- a/apps/dev/src/services/__tests__/docker.test.ts
+++ b/apps/dev/src/services/__tests__/docker.test.ts
@@ -425,8 +425,8 @@ describe('DockerService.ensureWorkerImage', () => {
it('honors custom Docker worker image and platform settings', async () => {
const rootDir = path.resolve(process.cwd(), '../..');
- process.env.DOCKER_WORKER_IMAGE = 'custom-worker:test';
- process.env.DOCKER_WORKER_PLATFORM = 'linux/arm64';
+ process.env.R_DOCKER_WORKER_IMAGE = 'custom-worker:test';
+ process.env.R_DOCKER_WORKER_PLATFORM = 'linux/arm64';
vi.mocked(execa)
.mockRejectedValueOnce(new Error('missing image'))
diff --git a/apps/dev/src/services/__tests__/ecosystem.test.ts b/apps/dev/src/services/__tests__/ecosystem.test.ts
index 6e6380d3..2a6dff8e 100644
--- a/apps/dev/src/services/__tests__/ecosystem.test.ts
+++ b/apps/dev/src/services/__tests__/ecosystem.test.ts
@@ -82,18 +82,18 @@ describe('ecosystem.config.js', () => {
it('defaults local services to Docker worker execution', () => {
process.env = {
...originalEnv,
- DEFAULT_COMPUTE_PROVIDER: undefined,
- DOCKER_WORKER_IMAGE: undefined,
- DOCKER_WORKER_PLATFORM: undefined,
+ R_DEFAULT_COMPUTE_PROVIDER: undefined,
+ R_DOCKER_WORKER_IMAGE: undefined,
+ R_DOCKER_WORKER_PLATFORM: undefined,
};
const apps = loadEcosystemApps();
const controllerApp = apps.find((app) => app.name === 'roomote-controller');
expect(controllerApp?.env).toMatchObject({
- DEFAULT_COMPUTE_PROVIDER: 'docker',
- DOCKER_WORKER_IMAGE: 'roomote-worker:local',
- DOCKER_WORKER_PLATFORM:
+ R_DEFAULT_COMPUTE_PROVIDER: 'docker',
+ R_DOCKER_WORKER_IMAGE: 'roomote-worker:local',
+ R_DOCKER_WORKER_PLATFORM:
process.arch === 'arm64' ? 'linux/arm64' : 'linux/amd64',
});
});
@@ -134,9 +134,9 @@ describe('ecosystem.config.js', () => {
expect(webApp?.env).toMatchObject({
R_PUBLIC_URL: 'https://roomote-example.ngrok.app',
R_APP_URL: 'https://roomote-example.ngrok.app',
- SLACK_REDIRECT_URI:
+ R_SLACK_REDIRECT_URI:
'https://roomote-example.ngrok.app/api/slack/callback',
- SLACK_AUTH_URI: 'https://roomote-example.ngrok.app/api/slack/auth',
+ R_SLACK_AUTH_URI: 'https://roomote-example.ngrok.app/api/slack/auth',
R_LINEAR_REDIRECT_URI:
'https://roomote-example.ngrok.app/api/linear/callback',
});
diff --git a/apps/dev/src/services/__tests__/pm2.test.ts b/apps/dev/src/services/__tests__/pm2.test.ts
index 2321f27f..93b93751 100644
--- a/apps/dev/src/services/__tests__/pm2.test.ts
+++ b/apps/dev/src/services/__tests__/pm2.test.ts
@@ -67,8 +67,8 @@ describe('PM2Service.startServices', () => {
R_APP_URL: 'https://roomote-example.ngrok.app',
USE_WORKER_RELEASE: 'false',
});
- expect(pm2Env).not.toHaveProperty('WORKER_RELEASE_CHANNEL');
- expect(pm2Env).not.toHaveProperty('WORKER_RELEASE_VERSION');
+ expect(pm2Env).not.toHaveProperty('R_WORKER_RELEASE_CHANNEL');
+ expect(pm2Env).not.toHaveProperty('R_WORKER_RELEASE_VERSION');
});
it('omits the pinned version env var when using the release channel without a pinned version', async () => {
@@ -81,9 +81,9 @@ describe('PM2Service.startServices', () => {
expect(pm2Env).toMatchObject({
USE_WORKER_RELEASE: 'true',
- WORKER_RELEASE_CHANNEL: 'stable',
+ R_WORKER_RELEASE_CHANNEL: 'stable',
});
- expect(pm2Env).not.toHaveProperty('WORKER_RELEASE_VERSION');
+ expect(pm2Env).not.toHaveProperty('R_WORKER_RELEASE_VERSION');
});
});
diff --git a/apps/dev/src/services/docker.ts b/apps/dev/src/services/docker.ts
index 2b0c47ad..b21eba58 100644
--- a/apps/dev/src/services/docker.ts
+++ b/apps/dev/src/services/docker.ts
@@ -55,9 +55,10 @@ export class DockerService {
];
static async ensureWorkerImage(verbose: boolean): Promise {
- const image = process.env.DOCKER_WORKER_IMAGE ?? this.DEFAULT_WORKER_IMAGE;
+ const image =
+ process.env.R_DOCKER_WORKER_IMAGE ?? this.DEFAULT_WORKER_IMAGE;
const platform =
- process.env.DOCKER_WORKER_PLATFORM ?? this.DEFAULT_WORKER_PLATFORM;
+ process.env.R_DOCKER_WORKER_PLATFORM ?? this.DEFAULT_WORKER_PLATFORM;
const rootDir = path.resolve(process.cwd(), '../..');
if (
diff --git a/apps/dev/src/services/pm2.ts b/apps/dev/src/services/pm2.ts
index 7672d276..dba0cf62 100644
--- a/apps/dev/src/services/pm2.ts
+++ b/apps/dev/src/services/pm2.ts
@@ -134,11 +134,11 @@ export class PM2Service {
}),
USE_WORKER_RELEASE: options.useRelease ? 'true' : 'false',
...(options.useRelease && {
- WORKER_RELEASE_CHANNEL: options.workerReleaseChannel,
+ R_WORKER_RELEASE_CHANNEL: options.workerReleaseChannel,
}),
...(options.useRelease &&
options.workerReleaseVersion && {
- WORKER_RELEASE_VERSION: options.workerReleaseVersion,
+ R_WORKER_RELEASE_VERSION: options.workerReleaseVersion,
}),
},
});
diff --git a/apps/docs/environment-variables.mdx b/apps/docs/environment-variables.mdx
index 104b6ffd..fea83c00 100644
--- a/apps/docs/environment-variables.mdx
+++ b/apps/docs/environment-variables.mdx
@@ -30,11 +30,11 @@ The exact order depends on the setting. A few important cases:
deployment env vars.
- Model role env vars such as `R_MODEL` override the same role selected
in **Settings > Models**.
-- Preview env vars such as `PREVIEW_PROXY_BASE_URL` override values saved from
+- Preview env vars such as `R_PREVIEW_PROXY_BASE_URL` override values saved from
**Settings > Live Previews**.
- Sandbox provider credentials resolve as process env first, then saved
deployment env vars. The default sandbox provider is special: an explicit
- admin selection saved in Roomote wins over `DEFAULT_COMPUTE_PROVIDER` because
+ admin selection saved in Roomote wins over `R_DEFAULT_COMPUTE_PROVIDER` because
local and Compose stacks often set a default automatically.
- Local development has safe defaults for Postgres, Redis, MinIO, signing keys,
and local URLs. Production does not.
@@ -67,8 +67,8 @@ Use environment variables when:
For production and shared self-hosted deployments:
- Set `NODE_ENV=production` and `R_APP_ENV=production`.
-- Set stable public URLs with `R_APP_URL`, `TRPC_URL`, and, when previews
- are enabled, `PREVIEW_PROXY_BASE_URL`.
+- Set stable public URLs with `R_APP_URL`, `R_TRPC_URL`, and, when previews
+ are enabled, `R_PREVIEW_PROXY_BASE_URL`.
- Provide `DATABASE_URL`, `REDIS_URL`, and S3-compatible artifact storage.
- Generate strong `ENCRYPTION_KEY`, `ARTIFACT_SIGNING_KEY`, and
`DASHBOARD_PASSWORD` values.
@@ -112,7 +112,7 @@ as per-task auth tokens or workspace paths.
| `R_APP_ENV` | Recommended | Roomote app environment: `development`, `preview`, or `production`. Also affects monitoring and local URL defaults. |
| `R_PUBLIC_URL` | Local callbacks | Stable public HTTPS origin used by local development for OAuth and webhook callbacks. |
| `R_APP_URL` | Production | Public web app origin. Workers and integrations use it for links back to Roomote. |
-| `TRPC_URL` | Production | API/tRPC origin used by workers and services. In single-origin production, this is often the app URL plus `/_roomote-api`. |
+| `R_TRPC_URL` | Production | API/tRPC origin used by workers and services. In single-origin production, this is often the app URL plus `/_roomote-api`. |
| `R_GITHUB_APP_SLUG` | GitHub setup | GitHub App slug used by server-rendered setup and GitHub integration flows. |
| `SETUP_TOKEN` | Required (non-local) | One-time bootstrap token that admits the first admin at `/setup`. Required on every non-local deployment — tokenless bootstrap is allowed only when `NODE_ENV` is not `production` and `R_APP_ENV` is `development`, so anything running with `NODE_ENV=production` needs it. Without it, first-admin bootstrap stays closed so nobody can claim the founding-admin slot by reaching the URL first. Optional only in local development. |
| `DASHBOARD_PASSWORD` | Production | Local fallback/admin password value used by the deployment. Generate a strong secret. |
@@ -189,23 +189,23 @@ as per-task auth tokens or workspace paths.
| Env var | Required | Used for |
| ------------------------------------ | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
-| `DEFAULT_COMPUTE_PROVIDER` | Optional | Runtime default sandbox provider when no admin default is saved. Supported values are `docker`, `modal`, `e2b`, `daytona`, and `blaxel`. |
-| `EXCLUDED_COMPUTE_PROVIDERS` | Optional | Comma-separated provider IDs to hide or exclude from default selection. |
-| `DOCKER_WORKER_IMAGE` | Optional | Worker image used by Docker. In production, prefer an immutable registry-qualified tag. |
-| `ROOMOTE_WORKER_IMAGE_REPO` | Optional | Registry repository used to derive the worker image from `RELEASE_VERSION` when `DOCKER_WORKER_IMAGE` is unset. |
-| `DOCKER_WORKER_PLATFORM` | Optional | Docker worker platform. Defaults to the host architecture (`linux/arm64` on arm hosts, `linux/amd64` otherwise). |
-| `DOCKER_WORKER_NETWORK` | Self-host Docker | Docker network for worker containers in Compose/self-host mode. |
-| `DOCKER_WORKER_RELEASE_PATH` | Optional | Path to a packaged worker release archive used by Docker worker bootstrap. |
-| `DOCKER_WORKER_CPU_LIMIT` | Optional | CPU limit per Docker task. Defaults to `2`. |
-| `DOCKER_WORKER_MEMORY_LIMIT` | Optional | Memory and memory+swap limit per Docker task. Defaults to `4g`. |
-| `DOCKER_WORKER_PIDS_LIMIT` | Optional | Process limit per Docker task. Defaults to `512`. |
-| `DOCKER_WORKER_DISK_LIMIT` | Optional | Writable-layer limit. Defaults to `20g`; tasks fail closed when the Docker storage driver cannot enforce it. |
-| `DOCKER_WORKER_ALLOW_UNBOUNDED_DISK` | Optional | Explicitly permits startup without `--storage-opt size`. Defaults to `false`; use only with an equivalent host-level quota. |
-| `DOCKER_WORKER_LOG_MAX_SIZE` | Optional | Maximum size of each Docker JSON log file. Defaults to `10m`. |
-| `DOCKER_WORKER_LOG_MAX_FILES` | Optional | Number of rotated Docker JSON log files retained. Defaults to `3`. |
-| `DOCKER_WORKER_EGRESS_POLICY` | Optional | `internet` (public egress; self-host blocks private+metadata ranges, local dev blocks metadata) or `none` (no external egress). |
-| `DOCKER_STANDBY_MAX_COUNT` | Optional | Maximum stopped Docker task containers retained for resume. Defaults to `10`; `0` disables retention. |
-| `DOCKER_STANDBY_MAX_AGE_HOURS` | Optional | Maximum age of a retained Docker task container. Defaults to `24`, capped at `168`. |
+| `R_DEFAULT_COMPUTE_PROVIDER` | Optional | Runtime default sandbox provider when no admin default is saved. Supported values are `docker`, `modal`, `e2b`, `daytona`, and `blaxel`. |
+| `R_EXCLUDED_COMPUTE_PROVIDERS` | Optional | Comma-separated provider IDs to hide or exclude from default selection. |
+| `R_DOCKER_WORKER_IMAGE` | Optional | Worker image used by Docker. In production, prefer an immutable registry-qualified tag. |
+| `ROOMOTE_WORKER_IMAGE_REPO` | Optional | Registry repository used to derive the worker image from `RELEASE_VERSION` when `R_DOCKER_WORKER_IMAGE` is unset. |
+| `R_DOCKER_WORKER_PLATFORM` | Optional | Docker worker platform. Defaults to the host architecture (`linux/arm64` on arm hosts, `linux/amd64` otherwise). |
+| `R_DOCKER_WORKER_NETWORK` | Self-host Docker | Docker network for worker containers in Compose/self-host mode. |
+| `R_DOCKER_WORKER_RELEASE_PATH` | Optional | Path to a packaged worker release archive used by Docker worker bootstrap. |
+| `R_DOCKER_WORKER_CPU_LIMIT` | Optional | CPU limit per Docker task. Defaults to `2`. |
+| `R_DOCKER_WORKER_MEMORY_LIMIT` | Optional | Memory and memory+swap limit per Docker task. Defaults to `4g`. |
+| `R_DOCKER_WORKER_PIDS_LIMIT` | Optional | Process limit per Docker task. Defaults to `512`. |
+| `R_DOCKER_WORKER_DISK_LIMIT` | Optional | Writable-layer limit. Defaults to `20g`; tasks fail closed when the Docker storage driver cannot enforce it. |
+| `R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK` | Optional | Explicitly permits startup without `--storage-opt size`. Defaults to `false`; use only with an equivalent host-level quota. |
+| `R_DOCKER_WORKER_LOG_MAX_SIZE` | Optional | Maximum size of each Docker JSON log file. Defaults to `10m`. |
+| `R_DOCKER_WORKER_LOG_MAX_FILES` | Optional | Number of rotated Docker JSON log files retained. Defaults to `3`. |
+| `R_DOCKER_WORKER_EGRESS_POLICY` | Optional | `internet` (public egress; self-host blocks private+metadata ranges, local dev blocks metadata) or `none` (no external egress). |
+| `R_DOCKER_STANDBY_MAX_COUNT` | Optional | Maximum stopped Docker task containers retained for resume. Defaults to `10`; `0` disables retention. |
+| `R_DOCKER_STANDBY_MAX_AGE_HOURS` | Optional | Maximum age of a retained Docker task container. Defaults to `24`, capped at `168`. |
| `MODAL_TOKEN_ID` | Modal | Modal token ID. Can also be saved from **Settings > Sandboxes**. |
| `MODAL_TOKEN_SECRET` | Modal | Modal token secret. |
| `MODAL_ENDPOINT` | Optional | Modal endpoint override. |
@@ -221,10 +221,10 @@ as per-task auth tokens or workspace paths.
| `BL_API_KEY` | Blaxel | Blaxel API key. Can also be saved from **Settings > Sandboxes**. |
| `BL_WORKSPACE` | Blaxel | Blaxel workspace name. |
| `BLAXEL_REGION` | Optional | Blaxel sandbox placement region. Unset lets Blaxel choose the closest region. |
-| `BLAXEL_STANDBY_MAX_COUNT` | Optional | Maximum Blaxel standby sandboxes retained for resume. Defaults to `25`; `0` disables retention. |
-| `BLAXEL_STANDBY_MAX_AGE_HOURS` | Optional | Maximum age of a Blaxel standby sandbox. Defaults to `168`, capped at `168`. |
-| `WORKER_RELEASE_CHANNEL` | Optional | Worker release channel, `stable` or `preview`, for hosted worker release selection. |
-| `WORKER_RELEASE_VERSION` | Optional | Explicit worker release version for hosted worker bootstrap. |
+| `R_BLAXEL_STANDBY_MAX_COUNT` | Optional | Maximum Blaxel standby sandboxes retained for resume. Defaults to `25`; `0` disables retention. |
+| `R_BLAXEL_STANDBY_MAX_AGE_HOURS` | Optional | Maximum age of a Blaxel standby sandbox. Defaults to `168`, capped at `168`. |
+| `R_WORKER_RELEASE_CHANNEL` | Optional | Worker release channel, `stable` or `preview`, for hosted worker release selection. |
+| `R_WORKER_RELEASE_VERSION` | Optional | Explicit worker release version for hosted worker bootstrap. |
| `LOCAL_SANDBOX_FILES_DIR` | Local development | Local override for sandbox bootstrap files. Used only by development workflows. |
### Source control providers
@@ -237,51 +237,51 @@ as per-task auth tokens or workspace paths.
| `R_GITHUB_CLIENT_ID` | GitHub | GitHub OAuth client ID. |
| `R_GITHUB_CLIENT_SECRET` | GitHub | GitHub OAuth client secret. |
| `R_GITHUB_WEBHOOK_SECRET` | GitHub | GitHub webhook secret. |
-| `GITHUB_MCP_SERVER_URL` | Optional | GitHub MCP server URL override. |
-| `GITHUB_AUTOMATED_SKIP_REPOS` | Optional | Repository skip list for automated GitHub processing. |
-| `GITHUB_AUTOMATED_SKIP_OWNERS` | Optional | Owner skip list for automated GitHub processing. |
+| `R_GITHUB_MCP_SERVER_URL` | Optional | GitHub MCP server URL override. |
+| `R_GITHUB_AUTOMATED_SKIP_REPOS` | Optional | Repository skip list for automated GitHub processing. |
+| `R_GITHUB_AUTOMATED_SKIP_OWNERS` | Optional | Owner skip list for automated GitHub processing. |
| `GITLAB_TOKEN` | GitLab | GitLab personal access token for source-control setup. |
-| `GITLAB_BASE_URL` | Optional | GitLab base URL for self-managed GitLab. |
-| `GITLAB_CLIENT_ID` | Optional | GitLab OAuth application ID for personal account linking and merge request comment triggers. |
-| `GITLAB_CLIENT_SECRET` | Optional | GitLab OAuth application secret for personal account linking and merge request comment triggers. |
-| `GITLAB_WEBHOOK_SIGNING_TOKEN` | Optional | GitLab webhook signing token. |
-| `GITLAB_WEBHOOK_SECRET` | Optional | GitLab webhook secret. |
-| `GITEA_BASE_URL` | Gitea | Gitea base URL. |
+| `R_GITLAB_BASE_URL` | Optional | GitLab base URL for self-managed GitLab. |
+| `R_GITLAB_CLIENT_ID` | Optional | GitLab OAuth application ID for personal account linking and merge request comment triggers. |
+| `R_GITLAB_CLIENT_SECRET` | Optional | GitLab OAuth application secret for personal account linking and merge request comment triggers. |
+| `R_GITLAB_WEBHOOK_SIGNING_TOKEN` | Optional | GitLab webhook signing token. |
+| `R_GITLAB_WEBHOOK_SECRET` | Optional | GitLab webhook secret. |
+| `R_GITEA_BASE_URL` | Gitea | Gitea base URL. |
| `GITEA_TOKEN` | Gitea | Gitea access token. |
-| `GITEA_USERNAME` | Optional | Gitea username. |
-| `GITEA_CLIENT_ID` | Optional | Gitea OAuth client ID. |
-| `GITEA_CLIENT_SECRET` | Optional | Gitea OAuth client secret. |
-| `GITEA_WEBHOOK_SECRET` | Optional | Gitea webhook secret. |
+| `R_GITEA_USERNAME` | Optional | Gitea username. |
+| `R_GITEA_CLIENT_ID` | Optional | Gitea OAuth client ID. |
+| `R_GITEA_CLIENT_SECRET` | Optional | Gitea OAuth client secret. |
+| `R_GITEA_WEBHOOK_SECRET` | Optional | Gitea webhook secret. |
| `BITBUCKET_TOKEN` | Bitbucket | Atlassian API token with Bitbucket scopes. |
-| `BITBUCKET_USERNAME` | Bitbucket | Atlassian account email that owns the API token. |
-| `BITBUCKET_BASE_URL` | Optional | Bitbucket base URL. Defaults to `https://bitbucket.org` (Cloud only). |
-| `BITBUCKET_CLIENT_ID` | Optional | Bitbucket OAuth client ID for personal account linking. |
-| `BITBUCKET_CLIENT_SECRET` | Optional | Bitbucket OAuth client secret for personal account linking. |
-| `BITBUCKET_WEBHOOK_SECRET` | Optional | Bitbucket webhook secret. |
-| `ADO_ORGANIZATION` | Azure DevOps | Azure DevOps organization. |
+| `R_BITBUCKET_USERNAME` | Bitbucket | Atlassian account email that owns the API token. |
+| `R_BITBUCKET_BASE_URL` | Optional | Bitbucket base URL. Defaults to `https://bitbucket.org` (Cloud only). |
+| `R_BITBUCKET_CLIENT_ID` | Optional | Bitbucket OAuth client ID for personal account linking. |
+| `R_BITBUCKET_CLIENT_SECRET` | Optional | Bitbucket OAuth client secret for personal account linking. |
+| `R_BITBUCKET_WEBHOOK_SECRET` | Optional | Bitbucket webhook secret. |
+| `R_ADO_ORGANIZATION` | Azure DevOps | Azure DevOps organization. |
| `ADO_TOKEN` | Azure DevOps | Azure DevOps access token. |
-| `ADO_BASE_URL` | Optional | Azure DevOps base URL. |
-| `ADO_USERNAME` | Optional | Azure DevOps username. |
-| `ADO_CLIENT_ID` | Optional | Microsoft Entra client ID for Azure DevOps OAuth. |
-| `ADO_CLIENT_SECRET` | Optional | Microsoft Entra client secret for Azure DevOps OAuth. |
-| `ADO_TENANT_ID` | Optional | Microsoft Entra tenant ID for Azure DevOps OAuth. `R_MICROSOFT_TENANT_ID` is also accepted. |
-| `ADO_WEBHOOK_SECRET` | Optional | Azure DevOps webhook secret. |
+| `R_ADO_BASE_URL` | Optional | Azure DevOps base URL. |
+| `R_ADO_USERNAME` | Optional | Azure DevOps username. |
+| `R_ADO_CLIENT_ID` | Optional | Microsoft Entra client ID for Azure DevOps OAuth. |
+| `R_ADO_CLIENT_SECRET` | Optional | Microsoft Entra client secret for Azure DevOps OAuth. |
+| `R_ADO_TENANT_ID` | Optional | Microsoft Entra tenant ID for Azure DevOps OAuth. `R_MICROSOFT_TENANT_ID` is also accepted. |
+| `R_ADO_WEBHOOK_SECRET` | Optional | Azure DevOps webhook secret. |
### Communications and sign-in
| Env var | Required | Used for |
| ------------------------------ | ----------------- | -------------------------------------------------------------------------------- |
-| `SLACK_APP_ID` | Slack app | Slack app ID for communications setup. |
+| `R_SLACK_APP_ID` | Slack app | Slack app ID for communications setup. |
| `R_SLACK_CLIENT_ID` | Slack app/auth | Slack OAuth client ID. Also accepted for Slack sign-in setup. |
| `R_SLACK_CLIENT_SECRET` | Slack app/auth | Slack OAuth client secret. Also accepted for Slack sign-in setup. |
-| `SLACK_REDIRECT_URI` | Optional | Slack redirect URI override. |
-| `SLACK_AUTH_URI` | Optional | Slack auth URI override. |
+| `R_SLACK_REDIRECT_URI` | Optional | Slack redirect URI override. |
+| `R_SLACK_AUTH_URI` | Optional | Slack auth URI override. |
| `R_SLACK_SIGNING_SECRET` | Slack app/auth | Slack signing secret for webhook verification. |
-| `SLACK_API_BASE_URL` | Optional | Slack API base URL. Defaults to `https://slack.com/api/`. |
-| `SLACK_UNFURL_ALLOWED_DOMAINS` | Optional | Domains Slack unfurl handling may allow. |
-| `SLACK_API_TIMEOUT_MS` | Optional | Slack API timeout. Defaults to `API_EXTERNAL_REQUEST_TIMEOUT_MS` or 10 seconds. |
-| `SLACK_DEBUG_LOGS` | Optional | Set to `true` or `1` to enable extra Slack logs. |
-| `ROUTER_DEBUG_CHANNEL_ID` | Optional | Slack channel ID for router debug output. |
+| `R_SLACK_API_BASE_URL` | Optional | Slack API base URL. Defaults to `https://slack.com/api/`. |
+| `R_SLACK_UNFURL_ALLOWED_DOMAINS` | Optional | Domains Slack unfurl handling may allow. |
+| `R_SLACK_API_TIMEOUT_MS` | Optional | Slack API timeout. Defaults to `R_API_EXTERNAL_REQUEST_TIMEOUT_MS` or 10 seconds. |
+| `R_SLACK_DEBUG_LOGS` | Optional | Set to `true` or `1` to enable extra Slack logs. |
+| `R_ROUTER_DEBUG_CHANNEL_ID` | Optional | Slack channel ID for router debug output. |
| `R_TEAMS_BOT_APP_ID` | Teams | Microsoft Teams bot app ID. |
| `R_TEAMS_BOT_APP_PASSWORD` | Teams | Microsoft Teams bot password. |
| `R_TEAMS_BOT_TENANT_ID` | Optional | Microsoft Teams bot tenant ID. |
@@ -320,10 +320,10 @@ apply semantics.
| Env var | Required | Used for |
| --------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------ |
-| `PREVIEW_PROXY_BASE_URL` | Preview support | Public preview-proxy origin. Overrides **Settings > Live Previews** when set. |
-| `PREVIEW_DOMAINS` | Optional | Comma-separated preview domains allowed by the preview proxy. Usually derived from `PREVIEW_PROXY_BASE_URL`. |
+| `R_PREVIEW_PROXY_BASE_URL` | Preview support | Public preview-proxy origin. Overrides **Settings > Live Previews** when set. |
+| `R_PREVIEW_DOMAINS` | Optional | Comma-separated preview domains allowed by the preview proxy. Usually derived from `R_PREVIEW_PROXY_BASE_URL`. |
| `ROOMOTE_PREVIEW_DOMAIN` | Optional | Roomote preview domain override used by preview runtime analysis. |
-| `PREVIEW_TOKEN_TTL_SECONDS` | Optional | Preview auth token lifetime. Defaults to 3600 seconds. |
+| `R_PREVIEW_TOKEN_TTL_SECONDS` | Optional | Preview auth token lifetime. Defaults to 3600 seconds. |
### Observability and request timing
@@ -335,10 +335,10 @@ apply semantics.
| `CONTROLLER_SENTRY_DSN` | Optional | Controller service Sentry DSN. Falls back to `SENTRY_DSN`. |
| `BULLMQ_SENTRY_DSN` | Optional | BullMQ service Sentry DSN. Falls back to `SENTRY_DSN`. |
| `WORKER_SENTRY_DSN` | Optional | Worker service Sentry DSN. Falls back to `SENTRY_DSN`. |
-| `API_DEBUG_LOGS` | Optional | Set to `true` or `1` to enable extra API logs. |
-| `API_EXTERNAL_REQUEST_TIMEOUT_MS` | Optional | Default external request timeout. Defaults to 10 seconds. |
-| `API_SLOW_REQUEST_THRESHOLD_MS` | Optional | Threshold for slow API request logging. Defaults to 5 seconds. |
-| `API_SLOW_EXTERNAL_REQUEST_THRESHOLD_MS` | Optional | Threshold for slow external request logging. Defaults to 2 seconds. |
+| `R_API_DEBUG_LOGS` | Optional | Set to `true` or `1` to enable extra API logs. |
+| `R_API_EXTERNAL_REQUEST_TIMEOUT_MS` | Optional | Default external request timeout. Defaults to 10 seconds. |
+| `R_API_SLOW_REQUEST_THRESHOLD_MS` | Optional | Threshold for slow API request logging. Defaults to 5 seconds. |
+| `R_API_SLOW_EXTERNAL_REQUEST_THRESHOLD_MS` | Optional | Threshold for slow external request logging. Defaults to 2 seconds. |
| `RELEASE_VERSION` | Release images | Baked release version used for release tagging and worker image derivation. |
| `GITHUB_SHA` | Release metadata | Commit SHA fallback for monitoring release metadata. |
| `VERCEL_GIT_COMMIT_SHA` | Release metadata | Vercel commit SHA fallback for monitoring release metadata. |
diff --git a/apps/docs/providers/communications/slack.mdx b/apps/docs/providers/communications/slack.mdx
index 4bbf5b79..ff22897f 100644
--- a/apps/docs/providers/communications/slack.mdx
+++ b/apps/docs/providers/communications/slack.mdx
@@ -35,7 +35,7 @@ R_SLACK_CLIENT_SECRET=...
R_SLACK_SIGNING_SECRET=...
```
-`SLACK_APP_ID` is optional for manual preconfiguration. Roomote saves Slack's
+`R_SLACK_APP_ID` is optional for manual preconfiguration. Roomote saves Slack's
`app_id` from the OAuth installation response after workspace install.
During OAuth install, Roomote also records the installed bot display name when
Slack exposes it, so setup and automation guidance can show the actual Slack
diff --git a/apps/docs/providers/compute/blaxel.mdx b/apps/docs/providers/compute/blaxel.mdx
index 05ca0519..f385de38 100644
--- a/apps/docs/providers/compute/blaxel.mdx
+++ b/apps/docs/providers/compute/blaxel.mdx
@@ -32,8 +32,8 @@ BLAXEL_REGION=us-pdx-1
Standby retention can be bounded separately from active task timeouts:
```sh
-BLAXEL_STANDBY_MAX_COUNT=25
-BLAXEL_STANDBY_MAX_AGE_HOURS=168
+R_BLAXEL_STANDBY_MAX_COUNT=25
+R_BLAXEL_STANDBY_MAX_AGE_HOURS=168
```
Region and standby retention are also editable under **Settings > Sandboxes >
diff --git a/apps/docs/providers/compute/docker.mdx b/apps/docs/providers/compute/docker.mdx
index dbd50999..108b7df5 100644
--- a/apps/docs/providers/compute/docker.mdx
+++ b/apps/docs/providers/compute/docker.mdx
@@ -31,24 +31,24 @@ from **Settings > Sandboxes**.
Common env vars:
```sh
-DEFAULT_COMPUTE_PROVIDER=docker
-DOCKER_WORKER_IMAGE=roomote-worker:local
+R_DEFAULT_COMPUTE_PROVIDER=docker
+R_DOCKER_WORKER_IMAGE=roomote-worker:local
# Optional: defaults to the host architecture (linux/amd64 or linux/arm64)
-DOCKER_WORKER_PLATFORM=linux/amd64
-DOCKER_WORKER_NETWORK=roomote_worker
-DOCKER_WORKER_RELEASE_PATH=/roomote/releases/worker-current.tar.gz
-DOCKER_WORKER_CPU_LIMIT=2
-DOCKER_WORKER_MEMORY_LIMIT=4g
-DOCKER_WORKER_PIDS_LIMIT=512
-DOCKER_WORKER_DISK_LIMIT=20g
-DOCKER_WORKER_ALLOW_UNBOUNDED_DISK=false
-DOCKER_WORKER_LOG_MAX_SIZE=10m
-DOCKER_WORKER_LOG_MAX_FILES=3
+R_DOCKER_WORKER_PLATFORM=linux/amd64
+R_DOCKER_WORKER_NETWORK=roomote_worker
+R_DOCKER_WORKER_RELEASE_PATH=/roomote/releases/worker-current.tar.gz
+R_DOCKER_WORKER_CPU_LIMIT=2
+R_DOCKER_WORKER_MEMORY_LIMIT=4g
+R_DOCKER_WORKER_PIDS_LIMIT=512
+R_DOCKER_WORKER_DISK_LIMIT=20g
+R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK=false
+R_DOCKER_WORKER_LOG_MAX_SIZE=10m
+R_DOCKER_WORKER_LOG_MAX_FILES=3
# internet allows public egress but blocks private/metadata ranges
# none disables external egress entirely
-DOCKER_WORKER_EGRESS_POLICY=internet
-DOCKER_STANDBY_MAX_COUNT=10
-DOCKER_STANDBY_MAX_AGE_HOURS=24
+R_DOCKER_WORKER_EGRESS_POLICY=internet
+R_DOCKER_STANDBY_MAX_COUNT=10
+R_DOCKER_STANDBY_MAX_AGE_HOURS=24
```
Local development builds `roomote-worker:local` automatically. Self-hosted
@@ -75,13 +75,13 @@ storage driver that supports `--storage-opt size`. When the driver rejects that
option, Roomote refuses to start the task rather than allowing untrusted code to
consume the host disk. Configure a quota-capable Docker data root. Operators
that enforce an equivalent host-level quota can explicitly set
-`DOCKER_WORKER_ALLOW_UNBOUNDED_DISK=true`; Roomote then logs a warning and
+`R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK=true`; Roomote then logs a warning and
starts without `--storage-opt size`.
Completed resumable tasks keep their stopped container and writable layer so a
follow-up can restart the same workspace. Roomote retains at most 10 containers
-for 24 hours by default. Configure `DOCKER_STANDBY_MAX_COUNT` and
-`DOCKER_STANDBY_MAX_AGE_HOURS` to change those bounds; a count of `0` disables
+for 24 hours by default. Configure `R_DOCKER_STANDBY_MAX_COUNT` and
+`R_DOCKER_STANDBY_MAX_AGE_HOURS` to change those bounds; a count of `0` disables
Docker standby retention. The five-minute retention sweep removes the oldest
or expired containers and their task networks.
@@ -98,7 +98,7 @@ Docker bridge gateway are also blocked without disrupting its use as the
public-egress next hop. Local development blocks metadata ranges but retains
private and gateway access because its API runs on the host. The worker never
receives the network-administration capability needed to remove the policy. Set
-`DOCKER_WORKER_EGRESS_POLICY=none` for tasks that need only the Roomote API and
+`R_DOCKER_WORKER_EGRESS_POLICY=none` for tasks that need only the Roomote API and
preview proxy.
## Security notes
@@ -123,9 +123,9 @@ repositories.
- **The worker image is missing.** Run the local dev server again, or publish
and configure a worker image for self-hosted production.
-- **The task cannot reach Roomote services.** Check `DOCKER_WORKER_NETWORK` and
+- **The task cannot reach Roomote services.** Check `R_DOCKER_WORKER_NETWORK` and
the Compose network used by the API and controller.
- **A worker reports that its disk limit is unsupported.** Configure a Docker
storage driver with per-container writable-layer quota support. Only set
- `DOCKER_WORKER_ALLOW_UNBOUNDED_DISK=true` when an equivalent host-level quota
+ `R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK=true` when an equivalent host-level quota
is already enforced.
diff --git a/apps/docs/providers/source-control/azure-devops.mdx b/apps/docs/providers/source-control/azure-devops.mdx
index 159bfa32..d540cce2 100644
--- a/apps/docs/providers/source-control/azure-devops.mdx
+++ b/apps/docs/providers/source-control/azure-devops.mdx
@@ -20,18 +20,18 @@ Save the organization and token in setup, deployment env vars, or local
`.env.local`:
```sh
-ADO_ORGANIZATION=
+R_ADO_ORGANIZATION=
ADO_TOKEN=
```
Optional values:
```sh
-ADO_BASE_URL=https://dev.azure.com
-ADO_USERNAME=ado
+R_ADO_BASE_URL=https://dev.azure.com
+R_ADO_USERNAME=ado
```
-`ADO_BASE_URL` defaults to `https://dev.azure.com`. `ADO_USERNAME` defaults to
+`R_ADO_BASE_URL` defaults to `https://dev.azure.com`. `R_ADO_USERNAME` defaults to
`ado` and is used as the Git-over-HTTPS username paired with the PAT.
## Sync repositories
diff --git a/apps/docs/providers/source-control/bitbucket.mdx b/apps/docs/providers/source-control/bitbucket.mdx
index 30678a74..c8f9a47b 100644
--- a/apps/docs/providers/source-control/bitbucket.mdx
+++ b/apps/docs/providers/source-control/bitbucket.mdx
@@ -35,21 +35,21 @@ or local `.env.local`:
```sh
BITBUCKET_TOKEN=
-BITBUCKET_USERNAME=
+R_BITBUCKET_USERNAME=
```
Optional values:
```sh
-BITBUCKET_BASE_URL=https://bitbucket.org
-BITBUCKET_WEBHOOK_SECRET=
+R_BITBUCKET_BASE_URL=https://bitbucket.org
+R_BITBUCKET_WEBHOOK_SECRET=
```
-`BITBUCKET_USERNAME` is required with `BITBUCKET_TOKEN` and holds the Atlassian
+`R_BITBUCKET_USERNAME` is required with `BITBUCKET_TOKEN` and holds the Atlassian
account email that owns the token (Bitbucket pairs the two for REST auth). For
git-over-HTTPS, Roomote automatically uses Bitbucket's static
`x-bitbucket-api-token-auth` user, so no extra configuration is needed.
-`BITBUCKET_BASE_URL` defaults to `https://bitbucket.org`. Only Bitbucket Cloud
+`R_BITBUCKET_BASE_URL` defaults to `https://bitbucket.org`. Only Bitbucket Cloud
is accepted; other hosts are rejected.
## Sync repositories
@@ -70,7 +70,7 @@ webhook for each synced Bitbucket repository. The webhook target is:
```
If only loopback Roomote URLs are configured, webhook setup is skipped and
-repository sync still succeeds. If `BITBUCKET_WEBHOOK_SECRET` is missing,
+repository sync still succeeds. If `R_BITBUCKET_WEBHOOK_SECRET` is missing,
Roomote generates and persists one as an encrypted deployment environment
variable.
@@ -92,8 +92,8 @@ Users can link their personal Bitbucket account for comment-triggered work when
a Bitbucket OAuth consumer is configured:
```sh
-BITBUCKET_CLIENT_ID=
-BITBUCKET_CLIENT_SECRET=
+R_BITBUCKET_CLIENT_ID=
+R_BITBUCKET_CLIENT_SECRET=
```
These credentials are optional. Without them, deployment-token repository sync,
diff --git a/apps/docs/providers/source-control/gitea.mdx b/apps/docs/providers/source-control/gitea.mdx
index 913712ee..66eaba66 100644
--- a/apps/docs/providers/source-control/gitea.mdx
+++ b/apps/docs/providers/source-control/gitea.mdx
@@ -28,18 +28,18 @@ Save the instance URL and token in setup, deployment env vars, or local
`.env.local`:
```sh
-GITEA_BASE_URL=https://git.example.com
+R_GITEA_BASE_URL=https://git.example.com
GITEA_TOKEN=
```
Optional values:
```sh
-GITEA_USERNAME=
-GITEA_WEBHOOK_SECRET=
+R_GITEA_USERNAME=
+R_GITEA_WEBHOOK_SECRET=
```
-When `GITEA_USERNAME` is omitted, Roomote resolves the token owner through the
+When `R_GITEA_USERNAME` is omitted, Roomote resolves the token owner through the
Gitea API and uses that login for Git-over-HTTPS credentials.
## Sync repositories
@@ -61,7 +61,7 @@ webhook for each synced Gitea repository. The webhook target is:
```
If only loopback Roomote URLs are configured, webhook setup is skipped and
-repository sync still succeeds. If `GITEA_WEBHOOK_SECRET` is missing, Roomote
+repository sync still succeeds. If `R_GITEA_WEBHOOK_SECRET` is missing, Roomote
generates and persists one as an encrypted deployment environment variable.
## Enable review automation
diff --git a/apps/docs/providers/source-control/gitlab.mdx b/apps/docs/providers/source-control/gitlab.mdx
index 92b425e8..f0bbece5 100644
--- a/apps/docs/providers/source-control/gitlab.mdx
+++ b/apps/docs/providers/source-control/gitlab.mdx
@@ -29,7 +29,7 @@ Save the token in setup, deployment env vars, or local `.env.local`:
GITLAB_TOKEN=
```
-For self-managed GitLab, also set `GITLAB_BASE_URL`.
+For self-managed GitLab, also set `R_GITLAB_BASE_URL`.
## Sync repositories
@@ -58,13 +58,13 @@ Preferred verification uses GitLab signed webhook headers. Set a GitLab
signing token on the webhook and save the same value in Roomote:
```sh
-GITLAB_WEBHOOK_SIGNING_TOKEN=
+R_GITLAB_WEBHOOK_SIGNING_TOKEN=
```
Legacy secret-token verification is also supported:
```sh
-GITLAB_WEBHOOK_SECRET=
+R_GITLAB_WEBHOOK_SECRET=
```
## Enable merge request comment triggers (recommended)
@@ -86,8 +86,8 @@ create one even though the fields are optional:
or local `.env.local`:
```sh
-GITLAB_CLIENT_ID=
-GITLAB_CLIENT_SECRET=
+R_GITLAB_CLIENT_ID=
+R_GITLAB_CLIENT_SECRET=
```
Once configured, a GitLab row appears under Personal settings > Linked
diff --git a/apps/preview-proxy/src/__tests__/fixtures.ts b/apps/preview-proxy/src/__tests__/fixtures.ts
index 1a64063c..03c59354 100644
--- a/apps/preview-proxy/src/__tests__/fixtures.ts
+++ b/apps/preview-proxy/src/__tests__/fixtures.ts
@@ -20,7 +20,7 @@ type MockConfig = {
PORT: string;
NODE_ENV: 'test';
R_APP_URL: string;
- PREVIEW_TOKEN_TTL_SECONDS: string;
+ R_PREVIEW_TOKEN_TTL_SECONDS: string;
PREVIEW_AUTH_COOKIE_NAME: string;
PREVIEW_PROXY_SUBDOMAIN_SUFFIX: string | undefined;
};
@@ -29,7 +29,7 @@ export const mockConfig: MockConfig = {
PORT: '0',
NODE_ENV: 'test',
R_APP_URL: 'https://api.example.com',
- PREVIEW_TOKEN_TTL_SECONDS: '3600',
+ R_PREVIEW_TOKEN_TTL_SECONDS: '3600',
PREVIEW_AUTH_COOKIE_NAME: 'preview_auth',
PREVIEW_PROXY_SUBDOMAIN_SUFFIX: undefined,
};
diff --git a/apps/preview-proxy/src/config.ts b/apps/preview-proxy/src/config.ts
index 4889c375..4f1c280c 100644
--- a/apps/preview-proxy/src/config.ts
+++ b/apps/preview-proxy/src/config.ts
@@ -7,7 +7,7 @@ const envSchema = z.object({
PORT: z.string().default('8081'),
R_APP_URL: z.string().default('http://localhost:13000'),
JOB_AUTH_PUBLIC_KEY: z.string().optional(),
- PREVIEW_TOKEN_TTL_SECONDS: z.string().default('3600'),
+ R_PREVIEW_TOKEN_TTL_SECONDS: z.string().default('3600'),
DATABASE_URL: z
.string()
.default(
diff --git a/apps/preview-proxy/src/handlers/auth-callback.ts b/apps/preview-proxy/src/handlers/auth-callback.ts
index 23386a05..96385ddd 100644
--- a/apps/preview-proxy/src/handlers/auth-callback.ts
+++ b/apps/preview-proxy/src/handlers/auth-callback.ts
@@ -119,7 +119,7 @@ export async function handleAuthCallback(
// treat localhost as a trustworthy origin for local development.
secure: true,
sameSite: 'None',
- maxAge: parseInt(config.PREVIEW_TOKEN_TTL_SECONDS),
+ maxAge: parseInt(config.R_PREVIEW_TOKEN_TTL_SECONDS),
path: '/',
domain: cookieDomain,
partitioned: true,
diff --git a/apps/preview-proxy/src/handlers/http.ts b/apps/preview-proxy/src/handlers/http.ts
index 5831fc5a..05870732 100644
--- a/apps/preview-proxy/src/handlers/http.ts
+++ b/apps/preview-proxy/src/handlers/http.ts
@@ -511,7 +511,7 @@ async function resolveAuthAndProxy(
// treat localhost as a trustworthy origin for local development.
secure: true,
sameSite: 'None',
- maxAge: parseInt(config.PREVIEW_TOKEN_TTL_SECONDS),
+ maxAge: parseInt(config.R_PREVIEW_TOKEN_TTL_SECONDS),
path: '/',
domain: cookieDomain,
partitioned: true,
diff --git a/apps/preview-proxy/src/lib/__tests__/cookies.test.ts b/apps/preview-proxy/src/lib/__tests__/cookies.test.ts
index 3d8cca97..cd751499 100644
--- a/apps/preview-proxy/src/lib/__tests__/cookies.test.ts
+++ b/apps/preview-proxy/src/lib/__tests__/cookies.test.ts
@@ -1,7 +1,7 @@
vi.mock('../../services/runtime-config', () => ({
getCachedPreviewRuntimeConfig: vi.fn(async () => ({
effective: {
- previewProxyBaseUrl: process.env.PREVIEW_PROXY_BASE_URL ?? null,
+ previewProxyBaseUrl: process.env.R_PREVIEW_PROXY_BASE_URL ?? null,
},
})),
}));
@@ -70,55 +70,57 @@ describe('buildSetCookieHeader', () => {
});
describe('getBaseDomain', () => {
- const originalEnv = process.env.PREVIEW_PROXY_BASE_URL;
+ const originalEnv = process.env.R_PREVIEW_PROXY_BASE_URL;
afterEach(() => {
if (originalEnv !== undefined) {
- process.env.PREVIEW_PROXY_BASE_URL = originalEnv;
+ process.env.R_PREVIEW_PROXY_BASE_URL = originalEnv;
} else {
- delete process.env.PREVIEW_PROXY_BASE_URL;
+ delete process.env.R_PREVIEW_PROXY_BASE_URL;
}
});
it('extracts hostname from production URL', async () => {
- process.env.PREVIEW_PROXY_BASE_URL = 'https://preview.roomote.example';
+ process.env.R_PREVIEW_PROXY_BASE_URL = 'https://preview.roomote.example';
await expect(getBaseDomain()).resolves.toBe('preview.roomote.example');
});
it('extracts hostname without port from development URL', async () => {
- process.env.PREVIEW_PROXY_BASE_URL = 'http://roomotepreview.localhost:8081';
+ process.env.R_PREVIEW_PROXY_BASE_URL =
+ 'http://roomotepreview.localhost:8081';
await expect(getBaseDomain()).resolves.toBe('roomotepreview.localhost');
});
it('returns empty string when env var is missing', async () => {
- delete process.env.PREVIEW_PROXY_BASE_URL;
+ delete process.env.R_PREVIEW_PROXY_BASE_URL;
await expect(getBaseDomain()).resolves.toBe('');
});
});
describe('getCookieDomain', () => {
- const originalEnv = process.env.PREVIEW_PROXY_BASE_URL;
+ const originalEnv = process.env.R_PREVIEW_PROXY_BASE_URL;
afterEach(() => {
if (originalEnv !== undefined) {
- process.env.PREVIEW_PROXY_BASE_URL = originalEnv;
+ process.env.R_PREVIEW_PROXY_BASE_URL = originalEnv;
} else {
- delete process.env.PREVIEW_PROXY_BASE_URL;
+ delete process.env.R_PREVIEW_PROXY_BASE_URL;
}
});
it('returns dotted domain for production URL', async () => {
- process.env.PREVIEW_PROXY_BASE_URL = 'https://preview.roomote.example';
+ process.env.R_PREVIEW_PROXY_BASE_URL = 'https://preview.roomote.example';
await expect(getCookieDomain()).resolves.toBe('.preview.roomote.example');
});
it('returns undefined for localhost URL', async () => {
- process.env.PREVIEW_PROXY_BASE_URL = 'http://roomotepreview.localhost:8081';
+ process.env.R_PREVIEW_PROXY_BASE_URL =
+ 'http://roomotepreview.localhost:8081';
await expect(getCookieDomain()).resolves.toBeUndefined();
});
it('returns undefined when env var is missing', async () => {
- delete process.env.PREVIEW_PROXY_BASE_URL;
+ delete process.env.R_PREVIEW_PROXY_BASE_URL;
await expect(getCookieDomain()).resolves.toBeUndefined();
});
});
diff --git a/apps/preview-proxy/src/lib/cookies.ts b/apps/preview-proxy/src/lib/cookies.ts
index 994f6420..738acf8d 100644
--- a/apps/preview-proxy/src/lib/cookies.ts
+++ b/apps/preview-proxy/src/lib/cookies.ts
@@ -1,7 +1,7 @@
import { getCachedPreviewRuntimeConfig } from '../services/runtime-config';
/**
- * Extracts the base domain from the PREVIEW_PROXY_BASE_URL for setting cookies.
+ * Extracts the base domain from the R_PREVIEW_PROXY_BASE_URL for setting cookies.
* The cookie is set on the base domain so it works across all preview subdomains.
*
* Examples:
diff --git a/apps/preview-proxy/src/lib/url-parser.ts b/apps/preview-proxy/src/lib/url-parser.ts
index 0c293ce5..008f80e9 100644
--- a/apps/preview-proxy/src/lib/url-parser.ts
+++ b/apps/preview-proxy/src/lib/url-parser.ts
@@ -29,7 +29,7 @@ const PORT_NAME_REGEX = /^[a-z0-9-]+$/;
* Task IDs are 13-character base36 strings generated by generateTaskId().
*
* Domain validation is NOT performed here. The main app's /api/auth/preview
- * endpoint validates redirect_uri against PREVIEW_DOMAINS for security.
+ * endpoint validates redirect_uri against R_PREVIEW_DOMAINS for security.
*/
export function parseHost(host: string): ParsedHost {
const invalid: ParsedHost = { portName: '', taskId: null, isValid: false };
diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts
index 914de3be..3ab764fe 100644
--- a/apps/web/next.config.ts
+++ b/apps/web/next.config.ts
@@ -62,11 +62,11 @@ const nextConfig: NextConfig = {
],
},
devIndicators: false,
- // When running inside a sandbox, PREVIEW_DOMAINS lists the base domains
+ // When running inside a sandbox, R_PREVIEW_DOMAINS lists the base domains
// used by preview-proxy (e.g. "preview-john.ngrok.app"). HMR WebSocket
// connections arrive with Origin set to the full preview subdomain
// (e.g. "taskid-web.preview-john.ngrok.app"), so we need *.domain entries.
- allowedDevOrigins: getAllowedDevOrigins(process.env.PREVIEW_DOMAINS),
+ allowedDevOrigins: getAllowedDevOrigins(process.env.R_PREVIEW_DOMAINS),
async redirects() {
// Public product docs now live at the standalone Mintlify site
// (https://docs.roomote.dev). Preserve old in-app /docs URLs by
diff --git a/apps/web/src/app/(onboarding)/setup/StepComputeConfig.client.test.tsx b/apps/web/src/app/(onboarding)/setup/StepComputeConfig.client.test.tsx
index b0ec9aae..b0013186 100644
--- a/apps/web/src/app/(onboarding)/setup/StepComputeConfig.client.test.tsx
+++ b/apps/web/src/app/(onboarding)/setup/StepComputeConfig.client.test.tsx
@@ -135,7 +135,7 @@ function buildComputeSetup(
persistedDefaultProvider: null,
setupSatisfied: false,
workerImage: {
- envVarName: 'DOCKER_WORKER_IMAGE',
+ envVarName: 'R_DOCKER_WORKER_IMAGE',
label: 'Worker Image',
runtimeSatisfied: false,
savedSatisfied: true,
@@ -176,7 +176,7 @@ describe('StepComputeConfig', () => {
{
{' '}
only works on this host. Set{' '}
- DOCKER_WORKER_IMAGE
+ R_DOCKER_WORKER_IMAGE
{' '}
to a pullable image before continuing.
diff --git a/apps/web/src/app/(onboarding)/setup/StepComputeProvider.client.test.tsx b/apps/web/src/app/(onboarding)/setup/StepComputeProvider.client.test.tsx
index 16c97012..adb3843c 100644
--- a/apps/web/src/app/(onboarding)/setup/StepComputeProvider.client.test.tsx
+++ b/apps/web/src/app/(onboarding)/setup/StepComputeProvider.client.test.tsx
@@ -59,7 +59,7 @@ describe('StepComputeProvider', () => {
persistedDefaultProvider: null,
setupSatisfied: false,
workerImage: {
- envVarName: 'DOCKER_WORKER_IMAGE',
+ envVarName: 'R_DOCKER_WORKER_IMAGE',
label: 'Worker Image',
runtimeSatisfied: false,
savedSatisfied: false,
diff --git a/apps/web/src/app/(onboarding)/setup/StepSourceControlConfig.tsx b/apps/web/src/app/(onboarding)/setup/StepSourceControlConfig.tsx
index 0b46311f..ac8a5c64 100644
--- a/apps/web/src/app/(onboarding)/setup/StepSourceControlConfig.tsx
+++ b/apps/web/src/app/(onboarding)/setup/StepSourceControlConfig.tsx
@@ -200,7 +200,7 @@ export function StepSourceControlConfig({
: window.location.origin;
const gitlabRedirectUri = `${publicOrigin}/api/auth/oauth2/callback/gitlab`;
const typedGitLabBaseUrl =
- values['GITLAB_BASE_URL']?.trim().replace(/\/+$/, '') ?? '';
+ values['R_GITLAB_BASE_URL']?.trim().replace(/\/+$/, '') ?? '';
const configuredGitLabBaseUrl =
sourceControlSetup.gitlabBaseUrl?.trim().replace(/\/+$/, '') ?? '';
const effectiveGitLabBaseUrl = /^https?:\/\//.test(typedGitLabBaseUrl)
diff --git a/apps/web/src/app/api/auth/preview/route.ts b/apps/web/src/app/api/auth/preview/route.ts
index 8fd0b12c..97d7dfe8 100644
--- a/apps/web/src/app/api/auth/preview/route.ts
+++ b/apps/web/src/app/api/auth/preview/route.ts
@@ -14,19 +14,19 @@ export const runtime = 'nodejs';
export async function GET(request: NextRequest) {
try {
- // CRITICAL: Require PREVIEW_DOMAINS to be configured - fail closed for security
+ // CRITICAL: Require R_PREVIEW_DOMAINS to be configured - fail closed for security
// Supports comma-separated list: "roomote-preview.dev,preview-john.ngrok.app,127.0.0.1.sslip.io"
const resolvedPreviewRuntimeConfig =
await resolveEffectivePreviewRuntimeConfig({
runtimeEnv: process.env,
- defaultPreviewProxyBaseUrl: Env.PREVIEW_PROXY_BASE_URL,
- defaultPreviewDomains: Env.PREVIEW_DOMAINS,
+ defaultPreviewProxyBaseUrl: Env.R_PREVIEW_PROXY_BASE_URL,
+ defaultPreviewDomains: Env.R_PREVIEW_DOMAINS,
});
const previewDomainsRaw =
resolvedPreviewRuntimeConfig.effective.previewDomains;
if (!previewDomainsRaw) {
- console.error('PREVIEW_DOMAINS environment variable is not configured');
+ console.error('R_PREVIEW_DOMAINS environment variable is not configured');
return NextResponse.json(
{ error: 'Service misconfigured' },
@@ -114,7 +114,7 @@ export async function GET(request: NextRequest) {
// Generate the preview auth token.
const token = await createPreviewToken({
userId,
- timeoutSeconds: Env.PREVIEW_TOKEN_TTL_SECONDS,
+ timeoutSeconds: Env.R_PREVIEW_TOKEN_TTL_SECONDS,
});
// Create the callback URL with the token.
diff --git a/apps/web/src/app/api/caddy/ask/__tests__/route.test.ts b/apps/web/src/app/api/caddy/ask/__tests__/route.test.ts
index 463217e7..5468a4cf 100644
--- a/apps/web/src/app/api/caddy/ask/__tests__/route.test.ts
+++ b/apps/web/src/app/api/caddy/ask/__tests__/route.test.ts
@@ -3,19 +3,19 @@ import { NextRequest } from 'next/server';
import { GET, isAllowedCaddyPreviewDomain } from '../route';
describe('GET /api/caddy/ask', () => {
- const originalPreviewProxyBaseUrl = process.env.PREVIEW_PROXY_BASE_URL;
+ const originalPreviewProxyBaseUrl = process.env.R_PREVIEW_PROXY_BASE_URL;
const originalRoomotePreviewDomain = process.env.ROOMOTE_PREVIEW_DOMAIN;
beforeEach(() => {
- process.env.PREVIEW_PROXY_BASE_URL = 'https://preview.roomote.test';
+ process.env.R_PREVIEW_PROXY_BASE_URL = 'https://preview.roomote.test';
delete process.env.ROOMOTE_PREVIEW_DOMAIN;
});
afterEach(() => {
if (originalPreviewProxyBaseUrl === undefined) {
- delete process.env.PREVIEW_PROXY_BASE_URL;
+ delete process.env.R_PREVIEW_PROXY_BASE_URL;
} else {
- process.env.PREVIEW_PROXY_BASE_URL = originalPreviewProxyBaseUrl;
+ process.env.R_PREVIEW_PROXY_BASE_URL = originalPreviewProxyBaseUrl;
}
if (originalRoomotePreviewDomain === undefined) {
diff --git a/apps/web/src/app/api/caddy/ask/route.ts b/apps/web/src/app/api/caddy/ask/route.ts
index a28ae8a1..96f73d24 100644
--- a/apps/web/src/app/api/caddy/ask/route.ts
+++ b/apps/web/src/app/api/caddy/ask/route.ts
@@ -26,8 +26,8 @@ async function getPreviewHostname(): Promise {
const resolvedPreviewRuntimeConfig =
await resolveEffectivePreviewRuntimeConfig({
runtimeEnv: process.env,
- defaultPreviewProxyBaseUrl: Env.PREVIEW_PROXY_BASE_URL,
- defaultPreviewDomains: Env.PREVIEW_DOMAINS,
+ defaultPreviewProxyBaseUrl: Env.R_PREVIEW_PROXY_BASE_URL,
+ defaultPreviewDomains: Env.R_PREVIEW_DOMAINS,
});
return normalizeHostname(
diff --git a/apps/web/src/app/api/teams/auth/__tests__/route.test.ts b/apps/web/src/app/api/teams/auth/__tests__/route.test.ts
index 6f27970d..67226c38 100644
--- a/apps/web/src/app/api/teams/auth/__tests__/route.test.ts
+++ b/apps/web/src/app/api/teams/auth/__tests__/route.test.ts
@@ -31,7 +31,7 @@ describe('GET /api/teams/auth', () => {
beforeEach(() => {
vi.clearAllMocks();
bootstrapWebRuntimeEnvMock.mockResolvedValue({
- TRPC_URL: 'https://api.example.com',
+ R_TRPC_URL: 'https://api.example.com',
});
authorizeMock.mockResolvedValue({
success: true,
diff --git a/apps/web/src/app/api/teams/auth/route.ts b/apps/web/src/app/api/teams/auth/route.ts
index ad573aa2..114f4162 100644
--- a/apps/web/src/app/api/teams/auth/route.ts
+++ b/apps/web/src/app/api/teams/auth/route.ts
@@ -190,7 +190,7 @@ export async function GET(request: NextRequest) {
const resumeResult = await resumePendingTeamsRequest({
stateToken,
- trpcUrl: env.TRPC_URL,
+ trpcUrl: env.R_TRPC_URL,
});
const resumeBody = resumeResult.body;
diff --git a/apps/web/src/app/api/webhooks/[...path]/__tests__/route.test.ts b/apps/web/src/app/api/webhooks/[...path]/__tests__/route.test.ts
index 8a83421f..6e8dd953 100644
--- a/apps/web/src/app/api/webhooks/[...path]/__tests__/route.test.ts
+++ b/apps/web/src/app/api/webhooks/[...path]/__tests__/route.test.ts
@@ -16,7 +16,7 @@ describe('POST /api/webhooks/[...path]', () => {
it('forwards to a pathful API base URL without dropping the prefix', async () => {
mockBootstrapWebRuntimeEnv.mockResolvedValue({
- TRPC_URL: 'https://app.roomote.test/_roomote-api',
+ R_TRPC_URL: 'https://app.roomote.test/_roomote-api',
});
const fetchMock = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
new Response('accepted', {
@@ -67,7 +67,7 @@ describe('POST /api/webhooks/[...path]', () => {
it('drops the Expect header Azure DevOps sends before forwarding', async () => {
mockBootstrapWebRuntimeEnv.mockResolvedValue({
- TRPC_URL: 'https://app.roomote.test/_roomote-api',
+ R_TRPC_URL: 'https://app.roomote.test/_roomote-api',
});
const fetchMock = vi
.spyOn(globalThis, 'fetch')
diff --git a/apps/web/src/app/api/webhooks/[...path]/route.ts b/apps/web/src/app/api/webhooks/[...path]/route.ts
index 602d9f28..aade3fcc 100644
--- a/apps/web/src/app/api/webhooks/[...path]/route.ts
+++ b/apps/web/src/app/api/webhooks/[...path]/route.ts
@@ -55,7 +55,7 @@ async function forwardWebhookRequest(
) {
const env = await bootstrapWebRuntimeEnv();
const path = await resolvePath(context);
- const targetUrl = resolveApiUrl(env.TRPC_URL, `/api/webhooks/${path}`);
+ const targetUrl = resolveApiUrl(env.R_TRPC_URL, `/api/webhooks/${path}`);
targetUrl.search = request.nextUrl.search;
const headers = removeHopByHopHeaders(request.headers);
diff --git a/apps/web/src/components/settings/ComputeProviderSection.client.test.tsx b/apps/web/src/components/settings/ComputeProviderSection.client.test.tsx
index b3320d4c..07c78549 100644
--- a/apps/web/src/components/settings/ComputeProviderSection.client.test.tsx
+++ b/apps/web/src/components/settings/ComputeProviderSection.client.test.tsx
@@ -129,7 +129,7 @@ describe('ComputeProviderSection advanced settings', () => {
supportsSnapshots: false,
fields: [
{
- envVarName: 'DOCKER_STANDBY_MAX_COUNT',
+ envVarName: 'R_DOCKER_STANDBY_MAX_COUNT',
label: 'Maximum retained tasks',
required: false,
category: 'infrastructure',
@@ -172,7 +172,7 @@ describe('ComputeProviderSection advanced settings', () => {
fireEvent.click(screen.getByRole('button', { name: /Save/ }));
expect(onSave).toHaveBeenCalledWith('docker', {
- DOCKER_STANDBY_MAX_COUNT: '4',
+ R_DOCKER_STANDBY_MAX_COUNT: '4',
});
});
diff --git a/apps/web/src/components/settings/ComputeProviderSection.tsx b/apps/web/src/components/settings/ComputeProviderSection.tsx
index 8c5c805d..a4ae0370 100644
--- a/apps/web/src/components/settings/ComputeProviderSection.tsx
+++ b/apps/web/src/components/settings/ComputeProviderSection.tsx
@@ -429,7 +429,7 @@ export function ComputeProviderSection({
Configure a registry-qualified worker image via{' '}
- DOCKER_WORKER_IMAGE
+ R_DOCKER_WORKER_IMAGE
{' '}
before selecting {provider.label} as the default.
diff --git a/apps/web/src/components/settings/SourceControl.test.tsx b/apps/web/src/components/settings/SourceControl.test.tsx
index bf47df83..103241e8 100644
--- a/apps/web/src/components/settings/SourceControl.test.tsx
+++ b/apps/web/src/components/settings/SourceControl.test.tsx
@@ -584,7 +584,7 @@ describe('SourceControl settings', () => {
{ provider: 'gitlab', configSatisfied: true },
{ provider: 'gitea', configSatisfied: true },
// configSatisfied covers required fields only, so ADO stays
- // unconfigured even when the optional ADO_TENANT_ID is satisfied via
+ // unconfigured even when the optional R_ADO_TENANT_ID is satisfied via
// the R_MICROSOFT_TENANT_ID fallback.
{ provider: 'ado', configSatisfied: false },
{ provider: 'bitbucket', configSatisfied: true },
diff --git a/apps/web/src/components/settings/SourceControl.tsx b/apps/web/src/components/settings/SourceControl.tsx
index 76686e4d..e9becb3f 100644
--- a/apps/web/src/components/settings/SourceControl.tsx
+++ b/apps/web/src/components/settings/SourceControl.tsx
@@ -928,7 +928,7 @@ function isProviderConfigured(
);
// configSatisfied covers required fields only: optional fields can be
- // satisfied by unrelated deployment config (for example ADO_TENANT_ID
+ // satisfied by unrelated deployment config (for example R_ADO_TENANT_ID
// falling back to R_MICROSOFT_TENANT_ID), which must not hide
// the provider's setup instructions.
return providerStatus?.configSatisfied ?? false;
diff --git a/apps/web/src/components/settings/previews/LivePreviewsSettings.client.test.tsx b/apps/web/src/components/settings/previews/LivePreviewsSettings.client.test.tsx
index f350a009..54fc7344 100644
--- a/apps/web/src/components/settings/previews/LivePreviewsSettings.client.test.tsx
+++ b/apps/web/src/components/settings/previews/LivePreviewsSettings.client.test.tsx
@@ -410,7 +410,7 @@ describe('LivePreviewsSettings', () => {
expect(input).toHaveAttribute('readonly');
expect(
screen.getByText(
- /Currently defined by PREVIEW_PROXY_BASE_URL, can't be changed here\./i,
+ /Currently defined by R_PREVIEW_PROXY_BASE_URL, can't be changed here\./i,
),
).toBeInTheDocument();
expect(screen.getByText('env-preview.roomote.test')).toBeInTheDocument();
@@ -485,7 +485,7 @@ describe('LivePreviewsSettings', () => {
status: 'fail',
reason: 'missing_runtime_config',
summary: 'Preview runtime config is incomplete.',
- details: ['PREVIEW_PROXY_BASE_URL is not configured.'],
+ details: ['R_PREVIEW_PROXY_BASE_URL is not configured.'],
checkedHostname: null,
};
diff --git a/apps/web/src/components/settings/previews/LivePreviewsSettings.tsx b/apps/web/src/components/settings/previews/LivePreviewsSettings.tsx
index e988f8c4..1b36667e 100644
--- a/apps/web/src/components/settings/previews/LivePreviewsSettings.tsx
+++ b/apps/web/src/components/settings/previews/LivePreviewsSettings.tsx
@@ -647,7 +647,7 @@ export function LivePreviewsSettings() {
{previewOriginManagedByEnv && (
- Currently defined by PREVIEW_PROXY_BASE_URL,
+ Currently defined by R_PREVIEW_PROXY_BASE_URL,
can't be changed here.
)}
diff --git a/apps/web/src/lib/server/__tests__/better-auth-base-url.test.ts b/apps/web/src/lib/server/__tests__/better-auth-base-url.test.ts
index cecf6096..223589f6 100644
--- a/apps/web/src/lib/server/__tests__/better-auth-base-url.test.ts
+++ b/apps/web/src/lib/server/__tests__/better-auth-base-url.test.ts
@@ -2,7 +2,7 @@ import { getBetterAuthBaseUrlConfig } from '../better-auth-base-url';
describe('getBetterAuthBaseUrlConfig', () => {
const originalAppEnv = process.env.R_APP_ENV;
- const originalPreviewDomains = process.env.PREVIEW_DOMAINS;
+ const originalPreviewDomains = process.env.R_PREVIEW_DOMAINS;
afterEach(() => {
if (originalAppEnv === undefined) {
@@ -12,9 +12,9 @@ describe('getBetterAuthBaseUrlConfig', () => {
}
if (originalPreviewDomains === undefined) {
- delete process.env.PREVIEW_DOMAINS;
+ delete process.env.R_PREVIEW_DOMAINS;
} else {
- process.env.PREVIEW_DOMAINS = originalPreviewDomains;
+ process.env.R_PREVIEW_DOMAINS = originalPreviewDomains;
}
});
diff --git a/apps/web/src/lib/server/__tests__/browser-origin-trust.test.ts b/apps/web/src/lib/server/__tests__/browser-origin-trust.test.ts
index cf1972ed..ad7393e1 100644
--- a/apps/web/src/lib/server/__tests__/browser-origin-trust.test.ts
+++ b/apps/web/src/lib/server/__tests__/browser-origin-trust.test.ts
@@ -2,7 +2,7 @@ import { assessBrowserOrigin } from '../browser-origin-trust';
describe('assessBrowserOrigin', () => {
const originalAppEnv = process.env.R_APP_ENV;
- const originalPreviewDomains = process.env.PREVIEW_DOMAINS;
+ const originalPreviewDomains = process.env.R_PREVIEW_DOMAINS;
afterEach(() => {
if (originalAppEnv === undefined) {
@@ -12,9 +12,9 @@ describe('assessBrowserOrigin', () => {
}
if (originalPreviewDomains === undefined) {
- delete process.env.PREVIEW_DOMAINS;
+ delete process.env.R_PREVIEW_DOMAINS;
} else {
- process.env.PREVIEW_DOMAINS = originalPreviewDomains;
+ process.env.R_PREVIEW_DOMAINS = originalPreviewDomains;
}
});
diff --git a/apps/web/src/lib/server/__tests__/env.test.ts b/apps/web/src/lib/server/__tests__/env.test.ts
index 973e2ea6..f91ba3e5 100644
--- a/apps/web/src/lib/server/__tests__/env.test.ts
+++ b/apps/web/src/lib/server/__tests__/env.test.ts
@@ -77,7 +77,7 @@ describe('web server Env wrapper', () => {
mockDotenvxGet.mockImplementation((key: string) => {
if (
key === 'ARTIFACT_SIGNING_KEY_PREVIOUS' ||
- key === 'PREVIEW_TOKEN_TTL_SECONDS'
+ key === 'R_PREVIEW_TOKEN_TTL_SECONDS'
) {
return undefined;
}
@@ -88,7 +88,7 @@ describe('web server Env wrapper', () => {
const { Env } = await import('../env');
expect(Env.ARTIFACT_SIGNING_KEY_PREVIOUS).toBeUndefined();
- expect(Env.PREVIEW_TOKEN_TTL_SECONDS).toBe(3600);
+ expect(Env.R_PREVIEW_TOKEN_TTL_SECONDS).toBe(3600);
} finally {
if (previousSkipEnvValidation === undefined) {
delete process.env.SKIP_ENV_VALIDATION;
@@ -307,13 +307,13 @@ describe('web server Env wrapper', () => {
expect(mockDotenvxConfig).toHaveBeenCalledTimes(2);
});
- it('uses the local preview domain default when PREVIEW_DOMAINS is missing outside production', async () => {
+ it('uses the local preview domain default when R_PREVIEW_DOMAINS is missing outside production', async () => {
const previousSkipEnvValidation = process.env.SKIP_ENV_VALIDATION;
delete process.env.SKIP_ENV_VALIDATION;
mockDotenvxGet.mockImplementation((key: string) => {
- if (key === 'PREVIEW_DOMAINS') {
+ if (key === 'R_PREVIEW_DOMAINS') {
return undefined;
}
@@ -325,7 +325,7 @@ describe('web server Env wrapper', () => {
rehydrateWebEnv();
- expect(Env.PREVIEW_DOMAINS).toBe(
+ expect(Env.R_PREVIEW_DOMAINS).toBe(
'localhost,127.0.0.1,roomotepreview.localhost',
);
} finally {
diff --git a/apps/web/src/lib/server/auth-provider-config.client.test.ts b/apps/web/src/lib/server/auth-provider-config.client.test.ts
index 150dbaef..6bba92c8 100644
--- a/apps/web/src/lib/server/auth-provider-config.client.test.ts
+++ b/apps/web/src/lib/server/auth-provider-config.client.test.ts
@@ -62,9 +62,9 @@ describe('resolveAuthProviderConfig', () => {
const config = await resolveAuthProviderConfig({
runtimeEnv: {},
deploymentEnvVars: {
- GITLAB_CLIENT_ID: 'gitlab-client-id',
- GITLAB_CLIENT_SECRET: 'gitlab-client-secret',
- GITLAB_BASE_URL: 'https://gitlab.example.com',
+ R_GITLAB_CLIENT_ID: 'gitlab-client-id',
+ R_GITLAB_CLIENT_SECRET: 'gitlab-client-secret',
+ R_GITLAB_BASE_URL: 'https://gitlab.example.com',
},
});
@@ -78,9 +78,9 @@ describe('resolveAuthProviderConfig', () => {
const config = await resolveAuthProviderConfig({
runtimeEnv: {},
deploymentEnvVars: {
- GITEA_CLIENT_ID: 'gitea-client-id',
- GITEA_CLIENT_SECRET: 'gitea-client-secret',
- GITEA_BASE_URL: 'https://gitea.example.com',
+ R_GITEA_CLIENT_ID: 'gitea-client-id',
+ R_GITEA_CLIENT_SECRET: 'gitea-client-secret',
+ R_GITEA_BASE_URL: 'https://gitea.example.com',
},
});
@@ -93,14 +93,14 @@ describe('resolveAuthProviderConfig', () => {
it('prefers process env Gitea OAuth credentials over deployment env vars', async () => {
const config = await resolveAuthProviderConfig({
runtimeEnv: {
- GITEA_CLIENT_ID: 'runtime-client-id',
- GITEA_CLIENT_SECRET: 'runtime-client-secret',
- GITEA_BASE_URL: 'https://runtime.gitea.example.com',
+ R_GITEA_CLIENT_ID: 'runtime-client-id',
+ R_GITEA_CLIENT_SECRET: 'runtime-client-secret',
+ R_GITEA_BASE_URL: 'https://runtime.gitea.example.com',
},
deploymentEnvVars: {
- GITEA_CLIENT_ID: 'deployment-client-id',
- GITEA_CLIENT_SECRET: 'deployment-client-secret',
- GITEA_BASE_URL: 'https://deployment.gitea.example.com',
+ R_GITEA_CLIENT_ID: 'deployment-client-id',
+ R_GITEA_CLIENT_SECRET: 'deployment-client-secret',
+ R_GITEA_BASE_URL: 'https://deployment.gitea.example.com',
},
});
@@ -113,11 +113,11 @@ describe('resolveAuthProviderConfig', () => {
const config = await resolveAuthProviderConfig({
runtimeEnv: {},
deploymentEnvVars: {
- ADO_CLIENT_ID: 'ado-client-id',
- ADO_CLIENT_SECRET: 'ado-client-secret',
- ADO_TENANT_ID: 'ado-tenant-id',
- ADO_ORGANIZATION: 'ado-org',
- ADO_BASE_URL: 'https://dev.azure.example.com',
+ R_ADO_CLIENT_ID: 'ado-client-id',
+ R_ADO_CLIENT_SECRET: 'ado-client-secret',
+ R_ADO_TENANT_ID: 'ado-tenant-id',
+ R_ADO_ORGANIZATION: 'ado-org',
+ R_ADO_BASE_URL: 'https://dev.azure.example.com',
},
});
@@ -134,9 +134,9 @@ describe('resolveAuthProviderConfig', () => {
runtimeEnv: {},
deploymentEnvVars: {
R_MICROSOFT_TENANT_ID: 'microsoft-tenant-id',
- ADO_CLIENT_ID: 'ado-client-id',
- ADO_CLIENT_SECRET: 'ado-client-secret',
- ADO_ORGANIZATION: 'ado-org',
+ R_ADO_CLIENT_ID: 'ado-client-id',
+ R_ADO_CLIENT_SECRET: 'ado-client-secret',
+ R_ADO_ORGANIZATION: 'ado-org',
},
});
diff --git a/apps/web/src/lib/server/auth-provider-config.ts b/apps/web/src/lib/server/auth-provider-config.ts
index b4c623f7..2fb9d157 100644
--- a/apps/web/src/lib/server/auth-provider-config.ts
+++ b/apps/web/src/lib/server/auth-provider-config.ts
@@ -97,50 +97,62 @@ export async function resolveAuthProviderConfig(
'R_MICROSOFT_TENANT_ID',
) ?? null;
const gitlabClientId =
- readConfiguredValue(runtimeEnv, deploymentEnvVars, 'GITLAB_CLIENT_ID') ??
+ readConfiguredValue(runtimeEnv, deploymentEnvVars, 'R_GITLAB_CLIENT_ID') ??
null;
const gitlabClientSecret =
readConfiguredValue(
runtimeEnv,
deploymentEnvVars,
- 'GITLAB_CLIENT_SECRET',
+ 'R_GITLAB_CLIENT_SECRET',
) ?? null;
const gitlabBaseUrl =
- readConfiguredValue(runtimeEnv, deploymentEnvVars, 'GITLAB_BASE_URL') ??
+ readConfiguredValue(runtimeEnv, deploymentEnvVars, 'R_GITLAB_BASE_URL') ??
null;
const giteaClientId =
- readConfiguredValue(runtimeEnv, deploymentEnvVars, 'GITEA_CLIENT_ID') ??
+ readConfiguredValue(runtimeEnv, deploymentEnvVars, 'R_GITEA_CLIENT_ID') ??
null;
const giteaClientSecret =
- readConfiguredValue(runtimeEnv, deploymentEnvVars, 'GITEA_CLIENT_SECRET') ??
- null;
+ readConfiguredValue(
+ runtimeEnv,
+ deploymentEnvVars,
+ 'R_GITEA_CLIENT_SECRET',
+ ) ?? null;
const giteaBaseUrl =
- readConfiguredValue(runtimeEnv, deploymentEnvVars, 'GITEA_BASE_URL') ??
+ readConfiguredValue(runtimeEnv, deploymentEnvVars, 'R_GITEA_BASE_URL') ??
null;
const bitbucketClientId =
- readConfiguredValue(runtimeEnv, deploymentEnvVars, 'BITBUCKET_CLIENT_ID') ??
- null;
+ readConfiguredValue(
+ runtimeEnv,
+ deploymentEnvVars,
+ 'R_BITBUCKET_CLIENT_ID',
+ ) ?? null;
const bitbucketClientSecret =
readConfiguredValue(
runtimeEnv,
deploymentEnvVars,
- 'BITBUCKET_CLIENT_SECRET',
+ 'R_BITBUCKET_CLIENT_SECRET',
) ?? null;
const bitbucketBaseUrl =
- readConfiguredValue(runtimeEnv, deploymentEnvVars, 'BITBUCKET_BASE_URL') ??
- null;
+ readConfiguredValue(
+ runtimeEnv,
+ deploymentEnvVars,
+ 'R_BITBUCKET_BASE_URL',
+ ) ?? null;
const adoClientId =
- readConfiguredValue(runtimeEnv, deploymentEnvVars, 'ADO_CLIENT_ID') ?? null;
+ readConfiguredValue(runtimeEnv, deploymentEnvVars, 'R_ADO_CLIENT_ID') ??
+ null;
const adoClientSecret =
- readConfiguredValue(runtimeEnv, deploymentEnvVars, 'ADO_CLIENT_SECRET') ??
+ readConfiguredValue(runtimeEnv, deploymentEnvVars, 'R_ADO_CLIENT_SECRET') ??
null;
const adoTenantId =
- readConfiguredValue(runtimeEnv, deploymentEnvVars, 'ADO_TENANT_ID') ?? null;
+ readConfiguredValue(runtimeEnv, deploymentEnvVars, 'R_ADO_TENANT_ID') ??
+ null;
const adoOrganization =
- readConfiguredValue(runtimeEnv, deploymentEnvVars, 'ADO_ORGANIZATION') ??
+ readConfiguredValue(runtimeEnv, deploymentEnvVars, 'R_ADO_ORGANIZATION') ??
null;
const adoBaseUrl =
- readConfiguredValue(runtimeEnv, deploymentEnvVars, 'ADO_BASE_URL') ?? null;
+ readConfiguredValue(runtimeEnv, deploymentEnvVars, 'R_ADO_BASE_URL') ??
+ null;
const enabledProviders = SETUP_AUTH_PROVIDER_CATALOG.filter((provider) =>
provider.id === 'slack'
diff --git a/apps/web/src/lib/server/auth.ts b/apps/web/src/lib/server/auth.ts
index 8e45463d..9e1ac541 100644
--- a/apps/web/src/lib/server/auth.ts
+++ b/apps/web/src/lib/server/auth.ts
@@ -292,7 +292,7 @@ function normalizeGitLabBaseUrl(baseUrl: string | null | undefined) {
}
// Unlike GitLab there is no default Gitea host: Gitea is always
-// instance-specific, so the provider stays disabled without GITEA_BASE_URL.
+// instance-specific, so the provider stays disabled without R_GITEA_BASE_URL.
function normalizeBitbucketBaseUrl(baseUrl: string | null | undefined) {
const trimmed = baseUrl?.trim();
@@ -817,7 +817,7 @@ async function createAuth(authProviderConfig: ResolvedAuthProviderConfig) {
return betterAuth({
appName: 'Roomote',
baseURL: getBetterAuthBaseUrlConfig({
- previewDomainsRaw: process.env.PREVIEW_DOMAINS,
+ previewDomainsRaw: process.env.R_PREVIEW_DOMAINS,
roomoteAppUrl: Env.R_APP_URL,
}),
secret: getBetterAuthSecret(),
diff --git a/apps/web/src/lib/server/preview-session.ts b/apps/web/src/lib/server/preview-session.ts
index 9e6806e6..1920ca50 100644
--- a/apps/web/src/lib/server/preview-session.ts
+++ b/apps/web/src/lib/server/preview-session.ts
@@ -47,14 +47,14 @@ async function validatePreviewUrlDomain(previewUrl: URL): Promise {
const resolvedPreviewRuntimeConfig =
await resolveEffectivePreviewRuntimeConfig({
runtimeEnv: process.env,
- defaultPreviewProxyBaseUrl: Env.PREVIEW_PROXY_BASE_URL,
- defaultPreviewDomains: Env.PREVIEW_DOMAINS,
+ defaultPreviewProxyBaseUrl: Env.R_PREVIEW_PROXY_BASE_URL,
+ defaultPreviewDomains: Env.R_PREVIEW_DOMAINS,
});
const previewDomainsRaw =
resolvedPreviewRuntimeConfig.effective.previewDomains;
if (!previewDomainsRaw) {
- console.error('PREVIEW_DOMAINS environment variable is not configured');
+ console.error('R_PREVIEW_DOMAINS environment variable is not configured');
throw new PreviewSessionError(500, 'Service misconfigured');
}
@@ -106,7 +106,7 @@ export async function createPreviewSession(params: {
const token = await createPreviewToken({
userId: authResult.userId,
- timeoutSeconds: Env.PREVIEW_TOKEN_TTL_SECONDS,
+ timeoutSeconds: Env.R_PREVIEW_TOKEN_TTL_SECONDS,
});
return {
diff --git a/apps/web/src/lib/server/slack-redirect-uri.ts b/apps/web/src/lib/server/slack-redirect-uri.ts
index 8d4d0f4f..6d2b0b35 100644
--- a/apps/web/src/lib/server/slack-redirect-uri.ts
+++ b/apps/web/src/lib/server/slack-redirect-uri.ts
@@ -2,7 +2,7 @@ import { Env } from '@/lib/server';
import { SLACK_APP_INSTALL_CALLBACK_PATH } from '@/lib/slack-callback-paths';
export function getSlackRedirectUri(): string {
- const configuredUri = Env.SLACK_REDIRECT_URI.trim();
+ const configuredUri = Env.R_SLACK_REDIRECT_URI.trim();
if (configuredUri) {
return configuredUri;
diff --git a/apps/web/src/trpc/commands/compute/compute-provisioning.ts b/apps/web/src/trpc/commands/compute/compute-provisioning.ts
index 675bc034..1032c202 100644
--- a/apps/web/src/trpc/commands/compute/compute-provisioning.ts
+++ b/apps/web/src/trpc/commands/compute/compute-provisioning.ts
@@ -222,7 +222,7 @@ function planComputeProvisioning({
const workerImageRef = resolveDerivedModalBaseImageRef({
...(runtimeEnv ?? process.env),
...(dockerWorkerImage !== undefined
- ? { DOCKER_WORKER_IMAGE: dockerWorkerImage }
+ ? { R_DOCKER_WORKER_IMAGE: dockerWorkerImage }
: {}),
});
diff --git a/apps/web/src/trpc/commands/compute/index.test.ts b/apps/web/src/trpc/commands/compute/index.test.ts
index 22137f94..76c14798 100644
--- a/apps/web/src/trpc/commands/compute/index.test.ts
+++ b/apps/web/src/trpc/commands/compute/index.test.ts
@@ -140,11 +140,11 @@ describe('compute commands', () => {
'BL_WORKSPACE',
'BLAXEL_IMAGE',
'BLAXEL_REGION',
- 'BLAXEL_STANDBY_MAX_COUNT',
- 'BLAXEL_STANDBY_MAX_AGE_HOURS',
- 'DOCKER_STANDBY_MAX_COUNT',
- 'DOCKER_STANDBY_MAX_AGE_HOURS',
- 'DOCKER_WORKER_IMAGE',
+ 'R_BLAXEL_STANDBY_MAX_COUNT',
+ 'R_BLAXEL_STANDBY_MAX_AGE_HOURS',
+ 'R_DOCKER_STANDBY_MAX_COUNT',
+ 'R_DOCKER_STANDBY_MAX_AGE_HOURS',
+ 'R_DOCKER_WORKER_IMAGE',
'RELEASE_VERSION',
];
const originalComputeEnv: Record = {};
@@ -249,8 +249,8 @@ describe('compute commands', () => {
await saveComputeConfigCommand(buildMockAuth(), {
provider: 'docker',
values: {
- DOCKER_STANDBY_MAX_COUNT: '4',
- DOCKER_STANDBY_MAX_AGE_HOURS: '12',
+ R_DOCKER_STANDBY_MAX_COUNT: '4',
+ R_DOCKER_STANDBY_MAX_AGE_HOURS: '12',
},
});
@@ -259,8 +259,8 @@ describe('compute commands', () => {
{
userId: 'compute-test-user',
values: [
- { name: 'DOCKER_STANDBY_MAX_COUNT', value: '4' },
- { name: 'DOCKER_STANDBY_MAX_AGE_HOURS', value: '12' },
+ { name: 'R_DOCKER_STANDBY_MAX_COUNT', value: '4' },
+ { name: 'R_DOCKER_STANDBY_MAX_AGE_HOURS', value: '12' },
],
},
);
@@ -270,7 +270,7 @@ describe('compute commands', () => {
await expect(
saveComputeConfigCommand(buildMockAuth(), {
provider: 'blaxel',
- values: { BLAXEL_STANDBY_MAX_AGE_HOURS: '169' },
+ values: { R_BLAXEL_STANDBY_MAX_AGE_HOURS: '169' },
}),
).rejects.toThrow('Retention period (hours) must be at most 168.');
});
@@ -281,7 +281,7 @@ describe('compute commands', () => {
values: {
MODAL_TOKEN_ID: 'token-id',
MODAL_TOKEN_SECRET: 'token-secret',
- DOCKER_WORKER_IMAGE: 'registry.example.com/worker:tag',
+ R_DOCKER_WORKER_IMAGE: 'registry.example.com/worker:tag',
},
});
@@ -296,7 +296,7 @@ describe('compute commands', () => {
]),
);
expect(values.map((entry) => entry.name)).not.toContain(
- 'DOCKER_WORKER_IMAGE',
+ 'R_DOCKER_WORKER_IMAGE',
);
});
@@ -402,7 +402,7 @@ describe('compute commands', () => {
});
it('starts the E2B template build when credentials are saved and a worker image is available', async () => {
- process.env.DOCKER_WORKER_IMAGE = 'registry.example.com/worker:tag';
+ process.env.R_DOCKER_WORKER_IMAGE = 'registry.example.com/worker:tag';
try {
await saveComputeConfigCommand(buildMockAuth(), {
@@ -410,7 +410,7 @@ describe('compute commands', () => {
values: { E2B_API_KEY: 'e2b-key' },
});
} finally {
- delete process.env.DOCKER_WORKER_IMAGE;
+ delete process.env.R_DOCKER_WORKER_IMAGE;
}
expect(mockUpsertDeploymentEnvironmentVariables).toHaveBeenCalledWith(
@@ -428,7 +428,7 @@ describe('compute commands', () => {
});
it('starts the Daytona snapshot registration when credentials are saved and a worker image is available', async () => {
- process.env.DOCKER_WORKER_IMAGE = 'registry.example.com/worker:tag';
+ process.env.R_DOCKER_WORKER_IMAGE = 'registry.example.com/worker:tag';
try {
await saveComputeConfigCommand(buildMockAuth(), {
@@ -436,7 +436,7 @@ describe('compute commands', () => {
values: { DAYTONA_API_KEY: 'daytona-key' },
});
} finally {
- delete process.env.DOCKER_WORKER_IMAGE;
+ delete process.env.R_DOCKER_WORKER_IMAGE;
}
expect(mockRunComputeProvisioning).toHaveBeenCalledWith({
@@ -452,7 +452,7 @@ describe('compute commands', () => {
provider: 'e2b',
values: {
E2B_API_KEY: 'e2b-key',
- DOCKER_WORKER_IMAGE: 'registry.example.com/worker:tag',
+ R_DOCKER_WORKER_IMAGE: 'registry.example.com/worker:tag',
},
});
@@ -474,7 +474,7 @@ describe('compute commands', () => {
});
it('derives and persists the Modal base image ref from the worker image', async () => {
- process.env.DOCKER_WORKER_IMAGE = 'registry.example.com/worker:tag';
+ process.env.R_DOCKER_WORKER_IMAGE = 'registry.example.com/worker:tag';
try {
await saveComputeConfigCommand(buildMockAuth(), {
@@ -485,7 +485,7 @@ describe('compute commands', () => {
},
});
} finally {
- delete process.env.DOCKER_WORKER_IMAGE;
+ delete process.env.R_DOCKER_WORKER_IMAGE;
}
expect(mockUpsertDeploymentEnvironmentVariables).toHaveBeenCalledWith(
@@ -605,8 +605,8 @@ describe('compute commands', () => {
await clearComputeConfigCommand(buildMockAuth(), { provider: 'docker' });
expect(txInArray).toHaveBeenCalledWith('env.name', [
- 'DOCKER_STANDBY_MAX_COUNT',
- 'DOCKER_STANDBY_MAX_AGE_HOURS',
+ 'R_DOCKER_STANDBY_MAX_COUNT',
+ 'R_DOCKER_STANDBY_MAX_AGE_HOURS',
]);
expect(txWhere).toHaveBeenCalled();
});
diff --git a/apps/web/src/trpc/commands/compute/index.ts b/apps/web/src/trpc/commands/compute/index.ts
index 8b68fd82..8e8d1f8f 100644
--- a/apps/web/src/trpc/commands/compute/index.ts
+++ b/apps/web/src/trpc/commands/compute/index.ts
@@ -107,7 +107,7 @@ export async function getComputeStatusCommand(auth: UserAuthSuccess): Promise<
> {
assertAdmin(auth);
- // Drop sticky DB-backed DOCKER_WORKER_IMAGE rows from the removed Settings
+ // Drop sticky DB-backed R_DOCKER_WORKER_IMAGE rows from the removed Settings
// editor so release-derived / process-env images always win.
await purgeSavedDeploymentWorkerImage();
@@ -166,7 +166,7 @@ export async function saveComputeConfigCommand(
getPersistedEnvironmentVariableNames(tx),
]);
- // DOCKER_WORKER_IMAGE may be submitted only for this request (setup).
+ // R_DOCKER_WORKER_IMAGE may be submitted only for this request (setup).
// Process env wins; uploaded/DB sticky values are not used.
const submittedWorkerImage =
input.values?.[SHARED_WORKER_IMAGE_ENV_VAR]?.trim() || null;
@@ -202,7 +202,7 @@ export async function saveComputeConfigCommand(
// Never accept a form-submitted MODAL_BASE_IMAGE_REF — derived only.
const derivedBaseImageRef = resolveDerivedModalBaseImageRef({
...process.env,
- DOCKER_WORKER_IMAGE: resolveEffectiveWorkerImageForSave(
+ R_DOCKER_WORKER_IMAGE: resolveEffectiveWorkerImageForSave(
effectiveSubmittedWorkerImage,
),
});
@@ -218,7 +218,7 @@ export async function saveComputeConfigCommand(
}
// Credentials and operator-editable infrastructure values are persisted as
- // encrypted deployment env vars. DOCKER_WORKER_IMAGE and managed artifacts
+ // encrypted deployment env vars. R_DOCKER_WORKER_IMAGE and managed artifacts
// (Modal base image, E2B/Daytona) are not form-sticky from the UI.
const valuesToSave: Array<{ name: string; value: string }> = [];
const envVarsToClear: string[] = [];
diff --git a/apps/web/src/trpc/commands/deployment/index.ts b/apps/web/src/trpc/commands/deployment/index.ts
index cff9ae50..f8b9cf4b 100644
--- a/apps/web/src/trpc/commands/deployment/index.ts
+++ b/apps/web/src/trpc/commands/deployment/index.ts
@@ -14,7 +14,7 @@ export function assessBrowserOriginCommand(input: {
}): BrowserOriginAssessment {
return assessBrowserOrigin({
browserOrigin: input.browserOrigin,
- previewDomainsRaw: Env.PREVIEW_DOMAINS,
+ previewDomainsRaw: Env.R_PREVIEW_DOMAINS,
roomoteAppUrl: Env.R_APP_URL,
});
}
diff --git a/apps/web/src/trpc/commands/github/mutations.test.ts b/apps/web/src/trpc/commands/github/mutations.test.ts
index e767db8c..30ac7452 100644
--- a/apps/web/src/trpc/commands/github/mutations.test.ts
+++ b/apps/web/src/trpc/commands/github/mutations.test.ts
@@ -53,7 +53,7 @@ vi.mock('@roomote/db/server', () => ({
vi.mock('@/lib/server', () => ({
Env: {
R_APP_URL: 'https://roomote.example.com',
- TRPC_URL: 'http://localhost:3000',
+ R_TRPC_URL: 'http://localhost:3000',
R_GITHUB_APP_SLUG: 'roomote',
R_GITHUB_CLIENT_ID: 'client-id',
R_GITHUB_CLIENT_SECRET: 'client-secret',
diff --git a/apps/web/src/trpc/commands/github/mutations.ts b/apps/web/src/trpc/commands/github/mutations.ts
index ab16ac70..79042264 100644
--- a/apps/web/src/trpc/commands/github/mutations.ts
+++ b/apps/web/src/trpc/commands/github/mutations.ts
@@ -189,10 +189,10 @@ function getGitHubCallbackUrl() {
}
function getGitHubWebhookUrl() {
- const trpcUrl = new URL(Env.TRPC_URL);
+ const trpcUrl = new URL(Env.R_TRPC_URL);
const webhookBaseUrl = isLoopbackHostname(trpcUrl.hostname)
? Env.R_APP_URL
- : Env.TRPC_URL;
+ : Env.R_TRPC_URL;
return new URL('/api/webhooks/github', webhookBaseUrl).toString();
}
@@ -462,7 +462,7 @@ async function revertPrCommit(
});
const client = createClient({
- url: Env.TRPC_URL,
+ url: Env.R_TRPC_URL,
headers: () => ({ Authorization: `Bearer ${authToken}` }),
});
diff --git a/apps/web/src/trpc/commands/misc-settings/index.ts b/apps/web/src/trpc/commands/misc-settings/index.ts
index e2aa7d22..573a3196 100644
--- a/apps/web/src/trpc/commands/misc-settings/index.ts
+++ b/apps/web/src/trpc/commands/misc-settings/index.ts
@@ -109,7 +109,7 @@ function getMissingRequiredEnvVars(): string[] {
'DATABASE_URL',
'REDIS_URL',
'R_APP_URL',
- 'TRPC_URL',
+ 'R_TRPC_URL',
'DASHBOARD_PASSWORD',
'ENCRYPTION_KEY',
'ARTIFACT_SIGNING_KEY',
@@ -126,7 +126,7 @@ function getMissingRequiredEnvVars(): string[] {
function hasPublicUrlCallbackMismatch(): DiagnosticsStatus {
const publicUrl = Env.R_PUBLIC_URL ?? Env.R_APP_URL;
const callbackUrls = [
- Env.SLACK_REDIRECT_URI,
+ Env.R_SLACK_REDIRECT_URI,
Env.R_LINEAR_REDIRECT_URI,
Env.R_TEAMS_BOT_TOKEN_ENDPOINT,
].filter(present);
@@ -148,8 +148,8 @@ function hasPublicUrlCallbackMismatch(): DiagnosticsStatus {
function hasWebhookSecretConfigured(): DiagnosticsStatus {
return [
Env.R_GITHUB_WEBHOOK_SECRET,
- Env.GITLAB_WEBHOOK_SECRET,
- Env.GITLAB_WEBHOOK_SIGNING_TOKEN,
+ Env.R_GITLAB_WEBHOOK_SECRET,
+ Env.R_GITLAB_WEBHOOK_SIGNING_TOKEN,
Env.R_SLACK_SIGNING_SECRET,
Env.R_TELEGRAM_WEBHOOK_SECRET,
Env.R_LINEAR_WEBHOOK_SECRET,
@@ -330,7 +330,7 @@ async function getProviderDiagnostics() {
]);
const comms: string[] = [];
- if ((slackActive[0]?.total ?? 0) > 0 || present(Env.SLACK_APP_ID)) {
+ if ((slackActive[0]?.total ?? 0) > 0 || present(Env.R_SLACK_APP_ID)) {
comms.push('slack');
}
if ((teamsActive[0]?.total ?? 0) > 0 || present(Env.R_TEAMS_BOT_APP_ID)) {
@@ -356,7 +356,7 @@ async function getProviderDiagnostics() {
]).size,
computeProvider:
settings?.runtimeComputeConfig?.defaultProvider ??
- Env.DEFAULT_COMPUTE_PROVIDER,
+ Env.R_DEFAULT_COMPUTE_PROVIDER,
};
}
@@ -386,8 +386,8 @@ async function collectDeploymentDiagnostics(): Promise {
providers,
] = await Promise.all([
getMigrationDiagnostics(),
- safeFetchStatus(Env.TRPC_URL),
- safeFetchStatus(Env.PREVIEW_PROXY_BASE_URL),
+ safeFetchStatus(Env.R_TRPC_URL),
+ safeFetchStatus(Env.R_PREVIEW_PROXY_BASE_URL),
getControllerHeartbeatStatus(),
getWorkerHeartbeatStatus(),
getObjectStorageWritableStatus(),
@@ -409,7 +409,7 @@ async function collectDeploymentDiagnostics(): Promise {
},
{
label: 'Docker image tag',
- value: parseDockerImageTag(Env.DOCKER_WORKER_IMAGE),
+ value: parseDockerImageTag(Env.R_DOCKER_WORKER_IMAGE),
},
{ label: 'Deployment mode', value: runtimeDiagnostics.appEnv },
],
diff --git a/apps/web/src/trpc/commands/preview-settings/index.ts b/apps/web/src/trpc/commands/preview-settings/index.ts
index 78dcf5ff..cc368d8a 100644
--- a/apps/web/src/trpc/commands/preview-settings/index.ts
+++ b/apps/web/src/trpc/commands/preview-settings/index.ts
@@ -236,8 +236,8 @@ async function validateRuntimePreviewConfig(): Promise<
> {
const resolvedConfig = await resolveEffectivePreviewRuntimeConfig({
runtimeEnv: process.env,
- defaultPreviewProxyBaseUrl: Env.PREVIEW_PROXY_BASE_URL,
- defaultPreviewDomains: Env.PREVIEW_DOMAINS,
+ defaultPreviewProxyBaseUrl: Env.R_PREVIEW_PROXY_BASE_URL,
+ defaultPreviewDomains: Env.R_PREVIEW_DOMAINS,
});
const analysis = resolvedConfig.analysis;
@@ -336,8 +336,8 @@ export async function getPreviewSettingsCommand(
getEnvironmentsCommand(auth),
resolveEffectivePreviewRuntimeConfig({
runtimeEnv: process.env,
- defaultPreviewProxyBaseUrl: Env.PREVIEW_PROXY_BASE_URL,
- defaultPreviewDomains: Env.PREVIEW_DOMAINS,
+ defaultPreviewProxyBaseUrl: Env.R_PREVIEW_PROXY_BASE_URL,
+ defaultPreviewDomains: Env.R_PREVIEW_DOMAINS,
}),
validateRuntimePreviewConfig(),
]);
@@ -495,12 +495,12 @@ export async function updatePreviewRuntimeConfigCommand(
) {
assertAdmin(auth);
- if ((process.env.PREVIEW_PROXY_BASE_URL ?? '').trim()) {
+ if ((process.env.R_PREVIEW_PROXY_BASE_URL ?? '').trim()) {
return {
success: false as const,
fieldErrors: {
previewProxyBaseUrl:
- 'This value is managed by PREVIEW_PROXY_BASE_URL in the runtime environment and cannot be changed here.',
+ 'This value is managed by R_PREVIEW_PROXY_BASE_URL in the runtime environment and cannot be changed here.',
},
};
}
diff --git a/apps/web/src/trpc/commands/sandbox-session/index.ts b/apps/web/src/trpc/commands/sandbox-session/index.ts
index a5e19c8f..96d91858 100644
--- a/apps/web/src/trpc/commands/sandbox-session/index.ts
+++ b/apps/web/src/trpc/commands/sandbox-session/index.ts
@@ -629,8 +629,8 @@ export async function getSandboxSessionByTaskIdCommand(
const resolvedPreviewRuntimeConfig =
await resolveEffectivePreviewRuntimeConfig({
runtimeEnv: process.env,
- defaultPreviewProxyBaseUrl: Env.PREVIEW_PROXY_BASE_URL,
- defaultPreviewDomains: Env.PREVIEW_DOMAINS,
+ defaultPreviewProxyBaseUrl: Env.R_PREVIEW_PROXY_BASE_URL,
+ defaultPreviewDomains: Env.R_PREVIEW_DOMAINS,
});
// Dynamically shorten the poll interval when a snapshot is in progress
diff --git a/apps/web/src/trpc/commands/setup-new/index.test.ts b/apps/web/src/trpc/commands/setup-new/index.test.ts
index 38be0ce7..fd1d916f 100644
--- a/apps/web/src/trpc/commands/setup-new/index.test.ts
+++ b/apps/web/src/trpc/commands/setup-new/index.test.ts
@@ -514,7 +514,7 @@ describe('setup-new source-control config commands', () => {
{
provider: 'gitea',
values: {
- GITEA_BASE_URL: 'https://gitea.example.com',
+ R_GITEA_BASE_URL: 'https://gitea.example.com',
GITEA_TOKEN: 'gitea-token',
},
},
@@ -530,7 +530,7 @@ describe('setup-new source-control config commands', () => {
expect.objectContaining({
userId: 'setup-test-user',
values: expect.arrayContaining([
- expect.objectContaining({ name: 'GITEA_BASE_URL' }),
+ expect.objectContaining({ name: 'R_GITEA_BASE_URL' }),
expect.objectContaining({ name: 'GITEA_TOKEN' }),
]),
}),
@@ -548,14 +548,14 @@ describe('setup-new source-control config commands', () => {
await saveSetupNewSourceControlConfigCommand(buildMockAuth(), {
provider: 'gitea',
values: {
- GITEA_BASE_URL: 'https://gitea.example.com',
+ R_GITEA_BASE_URL: 'https://gitea.example.com',
},
});
expect(mockUpsertDeploymentEnvironmentVariables).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
- values: [expect.objectContaining({ name: 'GITEA_BASE_URL' })],
+ values: [expect.objectContaining({ name: 'R_GITEA_BASE_URL' })],
}),
);
});
@@ -565,7 +565,7 @@ describe('setup-new source-control config commands', () => {
saveSetupNewSourceControlConfigCommand(buildMockAuth(), {
provider: 'gitea',
values: {
- GITEA_BASE_URL: 'https://gitea.example.com',
+ R_GITEA_BASE_URL: 'https://gitea.example.com',
},
}),
).rejects.toThrow(
@@ -585,7 +585,7 @@ describe('setup-new source-control config commands', () => {
saveSetupNewSourceControlConfigCommand(buildMockAuth(), {
provider: 'gitea',
values: {
- GITEA_BASE_URL: 'https://gitea.example.com',
+ R_GITEA_BASE_URL: 'https://gitea.example.com',
GITEA_TOKEN: 'gitea-token',
},
}),
@@ -648,7 +648,7 @@ describe('setup-new compute config commands', () => {
it('persists a Modal base image derived from the worker image', async () => {
vi.stubEnv(
- 'DOCKER_WORKER_IMAGE',
+ 'R_DOCKER_WORKER_IMAGE',
'ghcr.io/roocodeinc/roomote-worker:v1.2.3',
);
vi.stubEnv('MODAL_BASE_IMAGE_REF', '');
@@ -706,7 +706,7 @@ describe('setup-new compute config commands', () => {
});
it('rejects Modal when no base image default can be derived', async () => {
- vi.stubEnv('DOCKER_WORKER_IMAGE', 'roomote-worker:local');
+ vi.stubEnv('R_DOCKER_WORKER_IMAGE', 'roomote-worker:local');
vi.stubEnv('MODAL_BASE_IMAGE_REF', '');
await expect(
@@ -725,7 +725,7 @@ describe('setup-new compute config commands', () => {
});
it('ignores a form-submitted MODAL_BASE_IMAGE_REF when no derivable image exists', async () => {
- vi.stubEnv('DOCKER_WORKER_IMAGE', 'roomote-worker:local');
+ vi.stubEnv('R_DOCKER_WORKER_IMAGE', 'roomote-worker:local');
vi.stubEnv('MODAL_BASE_IMAGE_REF', '');
await expect(
@@ -746,7 +746,7 @@ describe('setup-new compute config commands', () => {
it('does not override a runtime-configured base image ref', async () => {
vi.stubEnv(
- 'DOCKER_WORKER_IMAGE',
+ 'R_DOCKER_WORKER_IMAGE',
'ghcr.io/roocodeinc/roomote-worker:v1.2.3',
);
vi.stubEnv('MODAL_BASE_IMAGE_REF', 'ghcr.io/custom/base-image:pinned');
@@ -772,7 +772,7 @@ describe('setup-new compute config commands', () => {
it('does not override an already persisted base image ref', async () => {
vi.stubEnv(
- 'DOCKER_WORKER_IMAGE',
+ 'R_DOCKER_WORKER_IMAGE',
'ghcr.io/roocodeinc/roomote-worker:v1.2.3',
);
vi.stubEnv('MODAL_BASE_IMAGE_REF', '');
@@ -800,7 +800,7 @@ describe('setup-new compute config commands', () => {
});
it('starts shared E2B provisioning after saving wizard credentials', async () => {
- vi.stubEnv('DOCKER_WORKER_IMAGE', 'registry.example.com/worker:tag');
+ vi.stubEnv('R_DOCKER_WORKER_IMAGE', 'registry.example.com/worker:tag');
const result = await saveSetupNewComputeConfigCommand(buildMockAuth(), {
provider: 'e2b',
@@ -831,7 +831,7 @@ describe('setup-new compute config commands', () => {
});
it('keeps the wizard pending when a fresh provisioning run is already in flight', async () => {
- vi.stubEnv('DOCKER_WORKER_IMAGE', 'registry.example.com/worker:tag');
+ vi.stubEnv('R_DOCKER_WORKER_IMAGE', 'registry.example.com/worker:tag');
mockDbTransaction.mockImplementationOnce(async (callback) => {
const tx = {
@@ -882,7 +882,7 @@ describe('setup-new compute config commands', () => {
});
it('ignores manual E2B template submissions and auto-provisions instead', async () => {
- vi.stubEnv('DOCKER_WORKER_IMAGE', 'registry.example.com/worker:tag');
+ vi.stubEnv('R_DOCKER_WORKER_IMAGE', 'registry.example.com/worker:tag');
const result = await saveSetupNewComputeConfigCommand(buildMockAuth(), {
provider: 'e2b',
@@ -904,13 +904,13 @@ describe('setup-new compute config commands', () => {
});
});
- it('uses a submitted worker image without sticky DOCKER_WORKER_IMAGE persist', async () => {
+ it('uses a submitted worker image without sticky R_DOCKER_WORKER_IMAGE persist', async () => {
const result = await saveSetupNewComputeConfigCommand(buildMockAuth(), {
provider: 'modal',
values: {
MODAL_TOKEN_ID: 'token-id',
MODAL_TOKEN_SECRET: 'token-secret',
- DOCKER_WORKER_IMAGE: 'registry.example.com/worker:tag',
+ R_DOCKER_WORKER_IMAGE: 'registry.example.com/worker:tag',
},
});
@@ -926,7 +926,7 @@ describe('setup-new compute config commands', () => {
]),
);
expect(values.map((entry) => entry.name)).not.toContain(
- 'DOCKER_WORKER_IMAGE',
+ 'R_DOCKER_WORKER_IMAGE',
);
});
});
diff --git a/apps/web/src/trpc/commands/setup-new/index.ts b/apps/web/src/trpc/commands/setup-new/index.ts
index b59a6b2b..ecff0ae0 100644
--- a/apps/web/src/trpc/commands/setup-new/index.ts
+++ b/apps/web/src/trpc/commands/setup-new/index.ts
@@ -1301,7 +1301,7 @@ export async function getSetupNewStatusCommand(auth: UserAuthSuccess) {
};
const sourceControlConnection = await getSourceControlConnectionSummary();
- const gitlabBaseUrl = await resolveDeploymentEnvVar('GITLAB_BASE_URL');
+ const gitlabBaseUrl = await resolveDeploymentEnvVar('R_GITLAB_BASE_URL');
const sourceControlSetup = buildSetupSourceControlStatus({
runtimeEnv: process.env,
persistedEnvVarNames: envVarNames,
@@ -1550,7 +1550,7 @@ export async function saveSetupNewComputeConfigCommand(
getPersistedEnvironmentVariableNames(tx),
]);
- // In-request DOCKER_WORKER_IMAGE is only used for this save/provisioning
+ // In-request R_DOCKER_WORKER_IMAGE is only used for this save/provisioning
// pass. It is not persisted as sticky deployment state; process env and
// RELEASE_VERSION derivation own runtime configuration.
const submittedWorkerImage =
@@ -1586,7 +1586,7 @@ export async function saveSetupNewComputeConfigCommand(
);
const derivedBaseImageRef = resolveDerivedModalBaseImageRef({
...process.env,
- DOCKER_WORKER_IMAGE: effectiveWorkerImage,
+ R_DOCKER_WORKER_IMAGE: effectiveWorkerImage,
});
if (
@@ -1641,7 +1641,7 @@ export async function saveSetupNewComputeConfigCommand(
if (provisioning.fieldPending) {
if (!provisioning.provisionable) {
throw new Error(
- `${providerStatus.label} needs a registry-qualified worker image (for example via DOCKER_WORKER_IMAGE) so Roomote can provision the worker base image automatically.`,
+ `${providerStatus.label} needs a registry-qualified worker image (for example via R_DOCKER_WORKER_IMAGE) so Roomote can provision the worker base image automatically.`,
);
}
diff --git a/apps/web/src/trpc/commands/setup/index.ts b/apps/web/src/trpc/commands/setup/index.ts
index beb75da3..e2e10264 100644
--- a/apps/web/src/trpc/commands/setup/index.ts
+++ b/apps/web/src/trpc/commands/setup/index.ts
@@ -208,7 +208,7 @@ Your job:
- If nothing else should be recommended, submit an empty list.
When you are ready, submit the final result with an authenticated POST request to:
-\`${'${ROOMOTE_PLATFORM_API_URL:-$TRPC_URL}'}/api/mcp/tasks/$ROOMOTE_TASK_ID/mcp_recommendations\`
+\`${'${ROOMOTE_PLATFORM_API_URL:-$R_TRPC_URL}'}/api/mcp/tasks/$ROOMOTE_TASK_ID/mcp_recommendations\`
Always include:
- \`Authorization: Bearer $ROOMOTE_CLOUD_TOKEN\`
diff --git a/apps/web/src/trpc/commands/slack/index.ts b/apps/web/src/trpc/commands/slack/index.ts
index 1e7858e4..3f998c58 100644
--- a/apps/web/src/trpc/commands/slack/index.ts
+++ b/apps/web/src/trpc/commands/slack/index.ts
@@ -113,7 +113,7 @@ async function resolveSlackOAuthConfig() {
return {
clientId: readConfiguredValue('R_SLACK_CLIENT_ID'),
clientSecret: readConfiguredValue('R_SLACK_CLIENT_SECRET'),
- appId: readConfiguredValue('SLACK_APP_ID'),
+ appId: readConfiguredValue('R_SLACK_APP_ID'),
};
}
diff --git a/apps/web/src/trpc/commands/source-control/index.test.ts b/apps/web/src/trpc/commands/source-control/index.test.ts
index 19565e6c..bca863d4 100644
--- a/apps/web/src/trpc/commands/source-control/index.test.ts
+++ b/apps/web/src/trpc/commands/source-control/index.test.ts
@@ -31,7 +31,7 @@ const {
mockValidateGiteaToken: vi.fn(),
mockEnv: {
R_APP_URL: 'https://roomote.example.com',
- TRPC_URL: 'http://localhost:3000/trpc',
+ R_TRPC_URL: 'http://localhost:3000/trpc',
},
}));
@@ -120,7 +120,7 @@ describe('source-control commands', () => {
beforeEach(() => {
vi.clearAllMocks();
mockEnv.R_APP_URL = 'https://roomote.example.com';
- mockEnv.TRPC_URL = 'http://localhost:3000/trpc';
+ mockEnv.R_TRPC_URL = 'http://localhost:3000/trpc';
mockResolveDeploymentEnvVar.mockResolvedValue(null);
mockEnvironmentMappingRows.rows = [
{ repositoryId: 'ado-repo-row-1' },
@@ -172,7 +172,7 @@ describe('source-control commands', () => {
userId: 'source-control-user',
values: [
expect.objectContaining({
- name: 'GITEA_WEBHOOK_SECRET',
+ name: 'R_GITEA_WEBHOOK_SECRET',
value: expect.stringMatching(/^[a-f0-9]{64}$/),
}),
],
@@ -276,7 +276,7 @@ describe('source-control commands', () => {
userId: 'source-control-user',
values: [
expect.objectContaining({
- name: 'ADO_WEBHOOK_SECRET',
+ name: 'R_ADO_WEBHOOK_SECRET',
value: expect.stringMatching(/^[a-f0-9]{64}$/),
}),
],
@@ -316,7 +316,7 @@ describe('source-control commands', () => {
assertValidSourceControlConfigInput({
provider: 'ado',
values: {
- ADO_ORGANIZATION: 'acme',
+ R_ADO_ORGANIZATION: 'acme',
ADO_TOKEN: 'ado-token',
},
}),
@@ -363,7 +363,7 @@ describe('source-control commands', () => {
assertValidSourceControlConfigInput({
provider: 'gitea',
values: {
- GITEA_BASE_URL: 'https://gitea.example.com',
+ R_GITEA_BASE_URL: 'https://gitea.example.com',
GITEA_TOKEN: 'gitea-token',
},
}),
diff --git a/apps/web/src/trpc/commands/source-control/index.ts b/apps/web/src/trpc/commands/source-control/index.ts
index 209984e3..fdcd7684 100644
--- a/apps/web/src/trpc/commands/source-control/index.ts
+++ b/apps/web/src/trpc/commands/source-control/index.ts
@@ -66,7 +66,7 @@ export async function getSourceControlConfigStatusCommand(
getPersistedEnvironmentVariableValues([
...NON_SECRET_SOURCE_CONTROL_ENV_VAR_NAMES,
]),
- resolveDeploymentEnvVar('GITLAB_BASE_URL'),
+ resolveDeploymentEnvVar('R_GITLAB_BASE_URL'),
]);
return buildSetupSourceControlStatus({
@@ -211,13 +211,13 @@ async function configureScopedProviderWebhooks<
function getSourceControlWebhookUrl(
provider: 'gitlab' | 'gitea' | 'bitbucket' | 'ado',
): string | null {
- // Match the GitHub App manifest behavior: prefer TRPC_URL, but fall back
- // to R_APP_URL when TRPC_URL is a loopback address that token-backed
+ // Match the GitHub App manifest behavior: prefer R_TRPC_URL, but fall back
+ // to R_APP_URL when R_TRPC_URL is a loopback address that token-backed
// source-control providers cannot reach.
- const trpcUrl = new URL(Env.TRPC_URL);
+ const trpcUrl = new URL(Env.R_TRPC_URL);
const webhookBaseUrl = isLoopbackHostname(trpcUrl.hostname)
? Env.R_APP_URL
- : Env.TRPC_URL;
+ : Env.R_TRPC_URL;
if (isLoopbackHostname(new URL(webhookBaseUrl).hostname)) {
return null;
@@ -229,7 +229,9 @@ function getSourceControlWebhookUrl(
async function resolveOrCreateGitLabWebhookSecret(
actorUserId: string,
): Promise {
- const existingSecret = await resolveDeploymentEnvVar('GITLAB_WEBHOOK_SECRET');
+ const existingSecret = await resolveDeploymentEnvVar(
+ 'R_GITLAB_WEBHOOK_SECRET',
+ );
if (existingSecret?.trim()) {
return existingSecret.trim();
@@ -239,7 +241,7 @@ async function resolveOrCreateGitLabWebhookSecret(
await upsertDeploymentEnvironmentVariables(db, {
userId: actorUserId,
- values: [{ name: 'GITLAB_WEBHOOK_SECRET', value: generatedSecret }],
+ values: [{ name: 'R_GITLAB_WEBHOOK_SECRET', value: generatedSecret }],
});
return generatedSecret;
@@ -296,7 +298,9 @@ async function configureGitLabWebhooks(
async function resolveOrCreateGiteaWebhookSecret(
actorUserId: string,
): Promise {
- const existingSecret = await resolveDeploymentEnvVar('GITEA_WEBHOOK_SECRET');
+ const existingSecret = await resolveDeploymentEnvVar(
+ 'R_GITEA_WEBHOOK_SECRET',
+ );
if (existingSecret?.trim()) {
return existingSecret.trim();
@@ -306,7 +310,7 @@ async function resolveOrCreateGiteaWebhookSecret(
await upsertDeploymentEnvironmentVariables(db, {
userId: actorUserId,
- values: [{ name: 'GITEA_WEBHOOK_SECRET', value: generatedSecret }],
+ values: [{ name: 'R_GITEA_WEBHOOK_SECRET', value: generatedSecret }],
});
return generatedSecret;
@@ -352,7 +356,7 @@ async function resolveOrCreateBitbucketWebhookSecret(
actorUserId: string,
): Promise {
const existingSecret = await resolveDeploymentEnvVar(
- 'BITBUCKET_WEBHOOK_SECRET',
+ 'R_BITBUCKET_WEBHOOK_SECRET',
);
if (existingSecret?.trim()) {
@@ -363,7 +367,7 @@ async function resolveOrCreateBitbucketWebhookSecret(
await upsertDeploymentEnvironmentVariables(db, {
userId: actorUserId,
- values: [{ name: 'BITBUCKET_WEBHOOK_SECRET', value: generatedSecret }],
+ values: [{ name: 'R_BITBUCKET_WEBHOOK_SECRET', value: generatedSecret }],
});
return generatedSecret;
@@ -408,7 +412,7 @@ async function configureBitbucketWebhooks(
async function resolveOrCreateAdoWebhookSecret(
actorUserId: string,
): Promise {
- const existingSecret = await resolveDeploymentEnvVar('ADO_WEBHOOK_SECRET');
+ const existingSecret = await resolveDeploymentEnvVar('R_ADO_WEBHOOK_SECRET');
if (existingSecret?.trim()) {
return existingSecret.trim();
@@ -418,7 +422,7 @@ async function resolveOrCreateAdoWebhookSecret(
await upsertDeploymentEnvironmentVariables(db, {
userId: actorUserId,
- values: [{ name: 'ADO_WEBHOOK_SECRET', value: generatedSecret }],
+ values: [{ name: 'R_ADO_WEBHOOK_SECRET', value: generatedSecret }],
});
return generatedSecret;
@@ -711,7 +715,7 @@ export async function assertValidSourceControlConfigInput(params: {
if (nextGiteaToken) {
const nextGiteaBaseUrl =
- params.values?.['GITEA_BASE_URL']?.trim() ??
+ params.values?.['R_GITEA_BASE_URL']?.trim() ??
(await Gitea.resolveGiteaBaseUrl());
if (!nextGiteaBaseUrl) {
@@ -730,19 +734,19 @@ export async function assertValidSourceControlConfigInput(params: {
if (nextBitbucketToken) {
const nextBitbucketUsername =
- params.values?.['BITBUCKET_USERNAME']?.trim() ??
+ params.values?.['R_BITBUCKET_USERNAME']?.trim() ??
(await Bitbucket.resolveBitbucketUsername());
if (!nextBitbucketUsername) {
throw new Error(
- 'BITBUCKET_USERNAME is required with BITBUCKET_TOKEN. Set it to the Atlassian account email that owns the API token.',
+ 'R_BITBUCKET_USERNAME is required with BITBUCKET_TOKEN. Set it to the Atlassian account email that owns the API token.',
);
}
const validation = await Bitbucket.validateBitbucketToken({
token: nextBitbucketToken,
username: nextBitbucketUsername,
- baseUrl: params.values?.['BITBUCKET_BASE_URL']?.trim() || undefined,
+ baseUrl: params.values?.['R_BITBUCKET_BASE_URL']?.trim() || undefined,
});
if (validation.status === 'invalid') {
@@ -752,7 +756,7 @@ export async function assertValidSourceControlConfigInput(params: {
if (nextAdoToken) {
const nextAdoOrganization =
- params.values?.['ADO_ORGANIZATION']?.trim() ??
+ params.values?.['R_ADO_ORGANIZATION']?.trim() ??
(await Ado.resolveAdoOrganization());
if (!nextAdoOrganization) {
@@ -762,7 +766,7 @@ export async function assertValidSourceControlConfigInput(params: {
const validation = await Ado.validateAdoToken({
token: nextAdoToken,
organization: nextAdoOrganization,
- baseUrl: params.values?.['ADO_BASE_URL']?.trim() || undefined,
+ baseUrl: params.values?.['R_ADO_BASE_URL']?.trim() || undefined,
});
if (validation.status === 'invalid') {
diff --git a/apps/web/src/trpc/commands/task-runs/index.test.ts b/apps/web/src/trpc/commands/task-runs/index.test.ts
index 76c95430..86b9e913 100644
--- a/apps/web/src/trpc/commands/task-runs/index.test.ts
+++ b/apps/web/src/trpc/commands/task-runs/index.test.ts
@@ -85,7 +85,7 @@ vi.mock('@roomote/slack', () => ({
vi.mock('@/lib/server', () => ({
Env: {
R_APP_URL: 'https://roomote.test',
- TRPC_URL: 'https://roomote.test/api/trpc',
+ R_TRPC_URL: 'https://roomote.test/api/trpc',
},
getArtifactById: vi.fn(),
getRepositories: (...args: unknown[]) => mockGetRepositories(...args),
diff --git a/apps/web/src/trpc/commands/task-runs/index.ts b/apps/web/src/trpc/commands/task-runs/index.ts
index 58b13f65..b2df54b4 100644
--- a/apps/web/src/trpc/commands/task-runs/index.ts
+++ b/apps/web/src/trpc/commands/task-runs/index.ts
@@ -312,7 +312,7 @@ export async function routeHomeTaskCommand(
userId: auth.userId,
taskDescription: trimmedDescription,
...(input.images?.length ? { images: input.images } : {}),
- apiBaseUrl: Env.TRPC_URL ?? Env.R_APP_URL,
+ apiBaseUrl: Env.R_TRPC_URL ?? Env.R_APP_URL,
});
return await routeTask(routingContext);
diff --git a/apps/worker/src/commands/__tests__/snapshot.test.ts b/apps/worker/src/commands/__tests__/snapshot.test.ts
index 9b7f4af1..590b6794 100644
--- a/apps/worker/src/commands/__tests__/snapshot.test.ts
+++ b/apps/worker/src/commands/__tests__/snapshot.test.ts
@@ -80,7 +80,7 @@ describe('snapshot', () => {
vi.clearAllMocks();
mockFetchSnapshotEnv.mockResolvedValue({
- envVars: { PREVIEW_PROXY_BASE_URL: 'https://preview.roomote.run' },
+ envVars: { R_PREVIEW_PROXY_BASE_URL: 'https://preview.roomote.run' },
gitHubToken: 'gh-token',
taskId: 'task-42',
});
diff --git a/apps/worker/src/commands/__tests__/utils.test.ts b/apps/worker/src/commands/__tests__/utils.test.ts
index b847f3c1..e0f67cba 100644
--- a/apps/worker/src/commands/__tests__/utils.test.ts
+++ b/apps/worker/src/commands/__tests__/utils.test.ts
@@ -56,7 +56,7 @@ const originalPath = process.env.PATH;
const originalAuthBypassValue = process.env.ROOMOTE_AUTH_BYPASS_VALUE;
const originalAuthBypassHeaderName =
process.env.ROOMOTE_AUTH_BYPASS_HEADER_NAME;
-const originalPreviewProxyBaseUrl = process.env.PREVIEW_PROXY_BASE_URL;
+const originalPreviewProxyBaseUrl = process.env.R_PREVIEW_PROXY_BASE_URL;
const originalPreviewProxySubdomainSuffix =
process.env.PREVIEW_PROXY_SUBDOMAIN_SUFFIX;
@@ -66,7 +66,7 @@ beforeEach(() => {
process.env.PATH = DEFAULT_PATH;
delete process.env.ROOMOTE_AUTH_BYPASS_VALUE;
delete process.env.ROOMOTE_AUTH_BYPASS_HEADER_NAME;
- delete process.env.PREVIEW_PROXY_BASE_URL;
+ delete process.env.R_PREVIEW_PROXY_BASE_URL;
delete process.env.PREVIEW_PROXY_SUBDOMAIN_SUFFIX;
});
@@ -85,9 +85,9 @@ afterEach(() => {
}
if (originalPreviewProxyBaseUrl === undefined) {
- delete process.env.PREVIEW_PROXY_BASE_URL;
+ delete process.env.R_PREVIEW_PROXY_BASE_URL;
} else {
- process.env.PREVIEW_PROXY_BASE_URL = originalPreviewProxyBaseUrl;
+ process.env.R_PREVIEW_PROXY_BASE_URL = originalPreviewProxyBaseUrl;
}
if (originalPreviewProxySubdomainSuffix === undefined) {
@@ -170,15 +170,15 @@ describe('injectEnvVars', () => {
);
});
- describe('PREVIEW_DOMAINS derivation', () => {
- it('derives PREVIEW_DOMAINS from the preview-proxy base URL hostname', async () => {
+ describe('R_PREVIEW_DOMAINS derivation', () => {
+ it('derives R_PREVIEW_DOMAINS from the preview-proxy base URL hostname', async () => {
const envVars: Record = {};
await injectEnvVars(envVars, undefined, {
previewProxyBaseUrl: 'https://preview.octomote.run',
});
- expect(envVars.PREVIEW_DOMAINS).toBe('preview.octomote.run');
+ expect(envVars.R_PREVIEW_DOMAINS).toBe('preview.octomote.run');
});
it('strips the port from the derived preview domain', async () => {
@@ -188,37 +188,37 @@ describe('injectEnvVars', () => {
previewProxyBaseUrl: 'http://roomotepreview.localhost:18081',
});
- expect(envVars.PREVIEW_DOMAINS).toBe('roomotepreview.localhost');
+ expect(envVars.R_PREVIEW_DOMAINS).toBe('roomotepreview.localhost');
});
- it('keeps a deployment-provided PREVIEW_DOMAINS value', async () => {
+ it('keeps a deployment-provided R_PREVIEW_DOMAINS value', async () => {
const envVars: Record = {
- PREVIEW_DOMAINS: 'custom.example.com',
+ R_PREVIEW_DOMAINS: 'custom.example.com',
};
await injectEnvVars(envVars, undefined, {
previewProxyBaseUrl: 'https://preview.octomote.run',
});
- expect(envVars.PREVIEW_DOMAINS).toBe('custom.example.com');
+ expect(envVars.R_PREVIEW_DOMAINS).toBe('custom.example.com');
});
it('falls back to the process env preview-proxy base URL', async () => {
const envVars: Record = {};
- process.env.PREVIEW_PROXY_BASE_URL = 'https://preview.roomote.dev';
+ process.env.R_PREVIEW_PROXY_BASE_URL = 'https://preview.roomote.dev';
await injectEnvVars(envVars);
- expect(envVars.PREVIEW_DOMAINS).toBe('preview.roomote.dev');
+ expect(envVars.R_PREVIEW_DOMAINS).toBe('preview.roomote.dev');
});
- it('does not set PREVIEW_DOMAINS without a preview-proxy base URL', async () => {
+ it('does not set R_PREVIEW_DOMAINS without a preview-proxy base URL', async () => {
const envVars: Record = {};
await injectEnvVars(envVars);
- expect(envVars.PREVIEW_DOMAINS).toBeUndefined();
+ expect(envVars.R_PREVIEW_DOMAINS).toBeUndefined();
});
it('ignores an invalid preview-proxy base URL', async () => {
@@ -228,7 +228,7 @@ describe('injectEnvVars', () => {
previewProxyBaseUrl: 'not-a-url',
});
- expect(envVars.PREVIEW_DOMAINS).toBeUndefined();
+ expect(envVars.R_PREVIEW_DOMAINS).toBeUndefined();
});
});
diff --git a/apps/worker/src/commands/setup/__tests__/setup-mcps.test.ts b/apps/worker/src/commands/setup/__tests__/setup-mcps.test.ts
index b1811906..d1380828 100644
--- a/apps/worker/src/commands/setup/__tests__/setup-mcps.test.ts
+++ b/apps/worker/src/commands/setup/__tests__/setup-mcps.test.ts
@@ -178,8 +178,8 @@ describe('resolveBuiltInMcpServers', () => {
);
});
- it('adds proxied Linear MCP from shared user MCP servers and preserves the TRPC_URL path prefix', () => {
- process.env.TRPC_URL = 'https://app.test.com/_roomote-api';
+ it('adds proxied Linear MCP from shared user MCP servers and preserves the R_TRPC_URL path prefix', () => {
+ process.env.R_TRPC_URL = 'https://app.test.com/_roomote-api';
const parsed = {
mcpServers: resolveBuiltInMcpServers(
@@ -215,8 +215,8 @@ describe('resolveBuiltInMcpServers', () => {
expect(linearConfig.headers.Authorization).toBe('Bearer test-cloud-token');
});
- it('falls back to R_APP_URL for proxied Linear MCP when TRPC_URL is not set', () => {
- delete process.env.TRPC_URL;
+ it('falls back to R_APP_URL for proxied Linear MCP when R_TRPC_URL is not set', () => {
+ delete process.env.R_TRPC_URL;
const parsed = {
mcpServers: resolveBuiltInMcpServers(
@@ -286,7 +286,7 @@ describe('resolveBuiltInMcpServers', () => {
});
it('rewrites user Notion MCP to the Roomote proxy with run token auth', () => {
- delete process.env.TRPC_URL;
+ delete process.env.R_TRPC_URL;
const parsed = {
mcpServers: resolveBuiltInMcpServers(
@@ -325,7 +325,7 @@ describe('resolveBuiltInMcpServers', () => {
});
it('adds the preview proxy bypass header for proxied integration MCPs when present in task env', () => {
- delete process.env.TRPC_URL;
+ delete process.env.R_TRPC_URL;
const parsed = {
mcpServers: resolveBuiltInMcpServers(
@@ -357,7 +357,7 @@ describe('resolveBuiltInMcpServers', () => {
});
it('rewrites user Sentry MCP to the Roomote proxy with run token auth', () => {
- delete process.env.TRPC_URL;
+ delete process.env.R_TRPC_URL;
const parsed = {
mcpServers: resolveBuiltInMcpServers(
@@ -401,7 +401,7 @@ describe('resolveBuiltInMcpServers', () => {
});
it('rewrites user Snowflake MCP to the Roomote proxy with run token auth', () => {
- delete process.env.TRPC_URL;
+ delete process.env.R_TRPC_URL;
const parsed = {
mcpServers: resolveBuiltInMcpServers(
@@ -438,7 +438,7 @@ describe('resolveBuiltInMcpServers', () => {
});
it('adds the preview proxy bypass header for proxied Snowflake MCPs when present in task env', () => {
- delete process.env.TRPC_URL;
+ delete process.env.R_TRPC_URL;
const parsed = {
mcpServers: resolveBuiltInMcpServers(
@@ -472,7 +472,7 @@ describe('resolveBuiltInMcpServers', () => {
});
it('rewrites user PostHog MCP to the Roomote proxy with run token auth', () => {
- delete process.env.TRPC_URL;
+ delete process.env.R_TRPC_URL;
const parsed = {
mcpServers: resolveBuiltInMcpServers(
@@ -518,7 +518,7 @@ describe('resolveBuiltInMcpServers', () => {
});
it('rewrites user Neon MCP to the Roomote proxy with run token auth', () => {
- delete process.env.TRPC_URL;
+ delete process.env.R_TRPC_URL;
const parsed = {
mcpServers: resolveBuiltInMcpServers(
@@ -557,7 +557,7 @@ describe('resolveBuiltInMcpServers', () => {
});
it('rewrites user Supabase MCP to the Roomote proxy with run token auth', () => {
- delete process.env.TRPC_URL;
+ delete process.env.R_TRPC_URL;
const parsed = {
mcpServers: resolveBuiltInMcpServers(
diff --git a/apps/worker/src/commands/setup/setup-mcps.ts b/apps/worker/src/commands/setup/setup-mcps.ts
index 63cc0712..3abb19e1 100644
--- a/apps/worker/src/commands/setup/setup-mcps.ts
+++ b/apps/worker/src/commands/setup/setup-mcps.ts
@@ -128,10 +128,10 @@ function normalizeApiBaseUrl(raw: string | undefined): string | undefined {
function resolveApiBaseUrl(
taskEnv: Record | undefined,
): string | undefined {
- // Prefer TRPC_URL (API origin) when available. R_APP_URL may point
+ // Prefer R_TRPC_URL (API origin) when available. R_APP_URL may point
// at the web app domain in some environments.
return (
- normalizeApiBaseUrl(process.env.TRPC_URL) ??
+ normalizeApiBaseUrl(process.env.R_TRPC_URL) ??
normalizeApiBaseUrl(taskEnv?.R_APP_URL)
);
}
diff --git a/apps/worker/src/commands/utils/env-vars.ts b/apps/worker/src/commands/utils/env-vars.ts
index c7906d5a..d0bdcc32 100644
--- a/apps/worker/src/commands/utils/env-vars.ts
+++ b/apps/worker/src/commands/utils/env-vars.ts
@@ -191,7 +191,7 @@ export async function injectEnvVars(
): Promise {
const identity = resolvePreviewIdentity(taskRun);
const previewProxyBaseUrl =
- options?.previewProxyBaseUrl ?? process.env.PREVIEW_PROXY_BASE_URL;
+ options?.previewProxyBaseUrl ?? process.env.R_PREVIEW_PROXY_BASE_URL;
const previewProxySubdomainSuffix =
options?.previewProxySubdomainSuffix ??
process.env.PREVIEW_PROXY_SUBDOMAIN_SUFFIX;
@@ -222,13 +222,13 @@ export async function injectEnvVars(
// Dev servers running in the sandbox (e.g. Next.js) reject cross-origin
// requests to internal dev resources (fonts, HMR, overlay assets) unless the
// preview hostname is allowlisted; apps derive that allowlist from
- // PREVIEW_DOMAINS. Deployment env vars only include PREVIEW_DOMAINS when the
+ // R_PREVIEW_DOMAINS. Deployment env vars only include R_PREVIEW_DOMAINS when the
// deployment persisted preview settings, so fall back to deriving it from
// the preview-proxy base URL the worker already has. An explicit
// deployment-provided value always wins.
- if (previewProxyBaseUrl && !envVars.PREVIEW_DOMAINS) {
+ if (previewProxyBaseUrl && !envVars.R_PREVIEW_DOMAINS) {
try {
- envVars.PREVIEW_DOMAINS = new URL(previewProxyBaseUrl).hostname;
+ envVars.R_PREVIEW_DOMAINS = new URL(previewProxyBaseUrl).hostname;
} catch {
// Ignore invalid base URLs; preview host construction above would have
// been skipped or failed for the same reason.
diff --git a/apps/worker/src/env/__tests__/substitute.test.ts b/apps/worker/src/env/__tests__/substitute.test.ts
index ea2248cb..052ec36e 100644
--- a/apps/worker/src/env/__tests__/substitute.test.ts
+++ b/apps/worker/src/env/__tests__/substitute.test.ts
@@ -12,15 +12,18 @@ describe('substituteEnvVars', () => {
it('should resolve ${VAR} references', () => {
const result = substituteEnvVars(
- { TRPC_URL: '${ROOMOTE_API_HOST}' },
+ { R_TRPC_URL: '${ROOMOTE_API_HOST}' },
lookup,
);
- expect(result.TRPC_URL).toBe('https://api.example.com');
+ expect(result.R_TRPC_URL).toBe('https://api.example.com');
});
it('should resolve bare $VAR references', () => {
- const result = substituteEnvVars({ TRPC_URL: '$ROOMOTE_API_HOST' }, lookup);
- expect(result.TRPC_URL).toBe('https://api.example.com');
+ const result = substituteEnvVars(
+ { R_TRPC_URL: '$ROOMOTE_API_HOST' },
+ lookup,
+ );
+ expect(result.R_TRPC_URL).toBe('https://api.example.com');
});
it('should handle multiple substitutions in one value', () => {
diff --git a/apps/worker/src/env/__tests__/worker-env.test.ts b/apps/worker/src/env/__tests__/worker-env.test.ts
index 40234d2f..b09e9a0a 100644
--- a/apps/worker/src/env/__tests__/worker-env.test.ts
+++ b/apps/worker/src/env/__tests__/worker-env.test.ts
@@ -51,8 +51,8 @@ describe('WorkerEnv', () => {
const env = workerEnv.buildSetupEnv();
expect(env).not.toHaveProperty('AUTH_TOKEN');
- expect(env).not.toHaveProperty('TRPC_URL');
- expect(env).not.toHaveProperty('PREVIEW_PROXY_BASE_URL');
+ expect(env).not.toHaveProperty('R_TRPC_URL');
+ expect(env).not.toHaveProperty('R_PREVIEW_PROXY_BASE_URL');
expect(env).not.toHaveProperty('PREVIEW_AUTH_PUBLIC_KEY');
expect(env).not.toHaveProperty('R_APP_URL');
});
@@ -71,7 +71,7 @@ describe('WorkerEnv', () => {
const env = workerEnv.buildServiceInstallEnv();
expect(env).not.toHaveProperty('AUTH_TOKEN');
- expect(env).not.toHaveProperty('TRPC_URL');
+ expect(env).not.toHaveProperty('R_TRPC_URL');
});
});
@@ -112,9 +112,9 @@ describe('WorkerEnv', () => {
const env = workerEnv.buildUserFacingEnv();
expect(env).not.toHaveProperty('AUTH_TOKEN');
- expect(env).not.toHaveProperty('TRPC_URL');
+ expect(env).not.toHaveProperty('R_TRPC_URL');
expect(env).not.toHaveProperty('PREVIEW_AUTH_PUBLIC_KEY');
- expect(env).not.toHaveProperty('PREVIEW_PROXY_BASE_URL');
+ expect(env).not.toHaveProperty('R_PREVIEW_PROXY_BASE_URL');
expect(env).not.toHaveProperty('R_APP_URL');
});
@@ -123,7 +123,7 @@ describe('WorkerEnv', () => {
HOME: '/home/worker',
PATH: '/usr/bin',
AUTH_TOKEN: 'secret-token',
- TRPC_URL: 'https://trpc.example.com',
+ R_TRPC_URL: 'https://trpc.example.com',
R_APP_URL: 'https://api.example.com',
R_MODEL: 'openrouter/openai/gpt-5.4',
R_SMALL_MODEL: 'openrouter/openai/gpt-5.4-mini',
@@ -270,10 +270,10 @@ describe('WorkerEnv', () => {
PATH: '/usr/bin',
LC_ALL: 'C.UTF-8',
AUTH_TOKEN: 'my-auth-token',
- TRPC_URL: 'https://trpc.example.com',
+ R_TRPC_URL: 'https://trpc.example.com',
JOB_AUTH_PRIVATE_KEY: 'job-private-key-data',
JOB_AUTH_PUBLIC_KEY: 'job-pk-data',
- PREVIEW_PROXY_BASE_URL: 'https://preview.example.com',
+ R_PREVIEW_PROXY_BASE_URL: 'https://preview.example.com',
PREVIEW_AUTH_PUBLIC_KEY: 'pk-data',
R_APP_URL: 'https://api.example.com',
R_APP_ENV: 'production',
@@ -288,7 +288,7 @@ describe('WorkerEnv', () => {
// But NOT present in any child process env
const userEnv = env.buildUserFacingEnv();
expect(userEnv).not.toHaveProperty('AUTH_TOKEN');
- expect(userEnv).not.toHaveProperty('TRPC_URL');
+ expect(userEnv).not.toHaveProperty('R_TRPC_URL');
expect(userEnv).not.toHaveProperty('PREVIEW_AUTH_PUBLIC_KEY');
expect(userEnv).not.toHaveProperty('R_APP_URL');
@@ -314,13 +314,13 @@ describe('WorkerEnv', () => {
NODE_ENV: 'test',
SKIP_ENV_VALIDATION: '1',
AUTH_TOKEN: 'secret-token',
- TRPC_URL: 'https://trpc.example.com',
+ R_TRPC_URL: 'https://trpc.example.com',
R_APP_URL: 'https://api.example.com',
R_APP_ENV: 'preview',
JOB_AUTH_PRIVATE_KEY: Buffer.from(privateKey).toString('base64'),
JOB_AUTH_PUBLIC_KEY: Buffer.from(publicKey).toString('base64'),
PREVIEW_AUTH_PUBLIC_KEY: 'outer-preview-key',
- PREVIEW_PROXY_BASE_URL: 'https://preview.example.com',
+ R_PREVIEW_PROXY_BASE_URL: 'https://preview.example.com',
};
const env = WorkerEnv.fromProcessEnv(fakeProcessEnv as NodeJS.ProcessEnv);
@@ -328,7 +328,7 @@ describe('WorkerEnv', () => {
expect(fakeProcessEnv.JOB_AUTH_PRIVATE_KEY).toBeUndefined();
expect(fakeProcessEnv.JOB_AUTH_PUBLIC_KEY).toBeUndefined();
expect(fakeProcessEnv.PREVIEW_AUTH_PUBLIC_KEY).toBeUndefined();
- expect(fakeProcessEnv.PREVIEW_PROXY_BASE_URL).toBeUndefined();
+ expect(fakeProcessEnv.R_PREVIEW_PROXY_BASE_URL).toBeUndefined();
expect(fakeProcessEnv.R_APP_ENV).toBeUndefined();
expect(env.jobAuthPublicKey).toBe(
@@ -394,7 +394,7 @@ describe('WorkerEnv', () => {
HOME: '/home/worker',
PATH: '/usr/bin',
AUTH_TOKEN: 'my-auth-token',
- TRPC_URL: 'https://trpc.example.com',
+ R_TRPC_URL: 'https://trpc.example.com',
R_APP_URL: 'https://api.example.com',
APP_ENV: 'preview',
} as NodeJS.ProcessEnv;
@@ -421,14 +421,14 @@ describe('WorkerEnv', () => {
PATH: '/usr/bin',
AUTH_TOKEN: 'token',
} as NodeJS.ProcessEnv),
- ).toThrow('TRPC_URL is not set');
+ ).toThrow('R_TRPC_URL is not set');
expect(() =>
WorkerEnv.fromProcessEnv({
HOME: '/home/worker',
PATH: '/usr/bin',
AUTH_TOKEN: 'token',
- TRPC_URL: 'https://trpc.example.com',
+ R_TRPC_URL: 'https://trpc.example.com',
} as NodeJS.ProcessEnv),
).toThrow('R_APP_URL is not set');
});
@@ -438,7 +438,7 @@ describe('WorkerEnv', () => {
HOME: '/home/worker',
PATH: '/usr/bin',
AUTH_TOKEN: 'token',
- TRPC_URL: 'https://trpc.example.com',
+ R_TRPC_URL: 'https://trpc.example.com',
R_APP_URL: 'https://api.example.com',
MISE_DATA_DIR: '/home/worker/.local/share/mise',
MISE_CACHE_DIR: '/home/worker/.cache/mise',
@@ -454,7 +454,7 @@ describe('WorkerEnv', () => {
HOME: '/home/worker',
PATH: '/usr/bin:/usr/local/bin',
AUTH_TOKEN: 'token',
- TRPC_URL: 'https://trpc.example.com',
+ R_TRPC_URL: 'https://trpc.example.com',
R_APP_URL: 'https://api.example.com',
} as NodeJS.ProcessEnv);
@@ -470,7 +470,7 @@ describe('WorkerEnv', () => {
HOME: '/home/worker',
PATH: '/usr/bin',
AUTH_TOKEN: 'secret-token',
- TRPC_URL: 'https://trpc.example.com',
+ R_TRPC_URL: 'https://trpc.example.com',
R_APP_URL: 'https://api.example.com',
R_APP_ENV: 'production',
ROOMOTE_APP_ENV: 'production',
@@ -478,7 +478,7 @@ describe('WorkerEnv', () => {
PREVIEW_AUTH_PUBLIC_KEY: 'outer-public-key',
PREVIEW_AUTH_COOKIE_NAME: 'preview_auth',
JOB_AUTH_PUBLIC_KEY: 'outer-job-key',
- PREVIEW_PROXY_BASE_URL: 'https://preview.example.com',
+ R_PREVIEW_PROXY_BASE_URL: 'https://preview.example.com',
PREVIEW_PROXY_SUBDOMAIN_SUFFIX: '.outer.example.com',
};
@@ -497,7 +497,7 @@ describe('WorkerEnv', () => {
expect(fakeProcessEnv.PREVIEW_AUTH_COOKIE_NAME).toBeUndefined();
expect(fakeProcessEnv.JOB_AUTH_PRIVATE_KEY).toBeUndefined();
expect(fakeProcessEnv.JOB_AUTH_PUBLIC_KEY).toBeUndefined();
- expect(fakeProcessEnv.PREVIEW_PROXY_BASE_URL).toBeUndefined();
+ expect(fakeProcessEnv.R_PREVIEW_PROXY_BASE_URL).toBeUndefined();
expect(fakeProcessEnv.PREVIEW_PROXY_SUBDOMAIN_SUFFIX).toBeUndefined();
expect(fakeProcessEnv.R_APP_ENV).toBeUndefined();
expect(fakeProcessEnv.ROOMOTE_APP_ENV).toBeUndefined();
@@ -509,26 +509,26 @@ describe('WorkerEnv', () => {
// Other keys are also kept
expect(fakeProcessEnv.HOME).toBe('/home/worker');
expect(fakeProcessEnv.PATH).toBeDefined();
- expect(fakeProcessEnv.TRPC_URL).toBe('https://trpc.example.com');
+ expect(fakeProcessEnv.R_TRPC_URL).toBe('https://trpc.example.com');
expect(fakeProcessEnv.R_APP_URL).toBe('https://api.example.com');
});
});
describe('environment isolation (dogfooding scenario)', () => {
- it('should prevent worker TRPC_URL from appearing in user-facing env', () => {
+ it('should prevent worker R_TRPC_URL from appearing in user-facing env', () => {
const env = WorkerEnv.fromProcessEnv({
HOME: '/home/worker',
PATH: '/usr/bin',
AUTH_TOKEN: 'token',
- TRPC_URL: 'https://internal-worker-trpc.example.com',
+ R_TRPC_URL: 'https://internal-worker-trpc.example.com',
R_APP_URL: 'https://internal-api.example.com',
} as NodeJS.ProcessEnv);
- // User project can set its own TRPC_URL without conflict
- env.addUserEnv({ TRPC_URL: 'https://my-project-trpc.example.com' });
+ // User project can set its own R_TRPC_URL without conflict
+ env.addUserEnv({ R_TRPC_URL: 'https://my-project-trpc.example.com' });
const userEnv = env.buildUserFacingEnv();
- expect(userEnv.TRPC_URL).toBe('https://my-project-trpc.example.com');
+ expect(userEnv.R_TRPC_URL).toBe('https://my-project-trpc.example.com');
// Worker's internal URL is only available via accessor
expect(env.roomoteAppUrl).toBe('https://internal-api.example.com');
diff --git a/apps/worker/src/env/worker-env.ts b/apps/worker/src/env/worker-env.ts
index 5ad5dbc0..74499c89 100644
--- a/apps/worker/src/env/worker-env.ts
+++ b/apps/worker/src/env/worker-env.ts
@@ -45,7 +45,7 @@ const SYSTEM_KEYS = [
const WORKER_INTERNAL_CONFIG_KEYS = ['R_APP_ENV', 'APP_ENV', 'ROOMOTE_APP_ENV'];
const BLOCKED_USER_FACING_ENV_KEYS = new Set([
'AUTH_TOKEN',
- 'TRPC_URL',
+ 'R_TRPC_URL',
'R_APP_URL',
// Legacy alias the controller injects for pre-rename snapshot workers;
// scrub it from task processes the same as R_APP_URL.
@@ -54,7 +54,7 @@ const BLOCKED_USER_FACING_ENV_KEYS = new Set([
'JOB_AUTH_PUBLIC_KEY',
'PREVIEW_AUTH_PUBLIC_KEY',
'PREVIEW_AUTH_COOKIE_NAME',
- 'PREVIEW_PROXY_BASE_URL',
+ 'R_PREVIEW_PROXY_BASE_URL',
'PREVIEW_PROXY_SUBDOMAIN_SUFFIX',
]);
const MODEL_RUNTIME_ENV_KEYS = [
@@ -203,7 +203,7 @@ export class WorkerEnv {
}
// Extract worker infrastructure secrets.
- const requiredVars = ['AUTH_TOKEN', 'TRPC_URL', 'R_APP_URL'] as const;
+ const requiredVars = ['AUTH_TOKEN', 'R_TRPC_URL', 'R_APP_URL'] as const;
for (const key of requiredVars) {
if (!processEnv[key]) {
@@ -213,9 +213,9 @@ export class WorkerEnv {
const workerConfig: WorkerConfig = {
authToken: processEnv.AUTH_TOKEN!,
- trpcUrl: processEnv.TRPC_URL!,
+ trpcUrl: processEnv.R_TRPC_URL!,
jobAuthPublicKey: processEnv.JOB_AUTH_PUBLIC_KEY,
- previewProxyBaseUrl: processEnv.PREVIEW_PROXY_BASE_URL,
+ previewProxyBaseUrl: processEnv.R_PREVIEW_PROXY_BASE_URL,
previewProxySubdomainSuffix: processEnv.PREVIEW_PROXY_SUBDOMAIN_SUFFIX,
previewAuthPublicKey: processEnv.PREVIEW_AUTH_PUBLIC_KEY,
previewAuthCookieName: processEnv.PREVIEW_AUTH_COOKIE_NAME,
@@ -243,7 +243,7 @@ export class WorkerEnv {
'PREVIEW_AUTH_PUBLIC_KEY',
'PREVIEW_AUTH_COOKIE_NAME',
'JOB_AUTH_PUBLIC_KEY',
- 'PREVIEW_PROXY_BASE_URL',
+ 'R_PREVIEW_PROXY_BASE_URL',
'PREVIEW_PROXY_SUBDOMAIN_SUFFIX',
];
diff --git a/apps/worker/src/mcp/roomote-mcp-server/__tests__/config.test.ts b/apps/worker/src/mcp/roomote-mcp-server/__tests__/config.test.ts
index 669b442c..59a50506 100644
--- a/apps/worker/src/mcp/roomote-mcp-server/__tests__/config.test.ts
+++ b/apps/worker/src/mcp/roomote-mcp-server/__tests__/config.test.ts
@@ -9,7 +9,7 @@ describe('roomote mcp config helpers', () => {
delete process.env.AUTH_TOKEN;
delete process.env.ROOMOTE_CLOUD_TOKEN;
delete process.env.ROOMOTE_PLATFORM_API_URL;
- delete process.env.TRPC_URL;
+ delete process.env.R_TRPC_URL;
delete process.env.ROOMOTE_WORKSPACE_PATH;
delete process.env.ROOMOTE_AUTH_BYPASS_HEADER_NAME;
delete process.env.ROOMOTE_AUTH_BYPASS_VALUE;
@@ -36,7 +36,7 @@ describe('roomote mcp config helpers', () => {
it('uses the configured platform API URL for roomote requests', () => {
process.env.ROOMOTE_CLOUD_TOKEN = 'run-token';
- process.env.TRPC_URL = 'https://trpc.example.com';
+ process.env.R_TRPC_URL = 'https://trpc.example.com';
expect(getRoomoteConfig()).toEqual({
token: 'run-token',
@@ -72,7 +72,7 @@ describe('roomote mcp config helpers', () => {
it('falls back to AUTH_TOKEN when ROOMOTE_CLOUD_TOKEN is unavailable', () => {
process.env.AUTH_TOKEN = 'worker-run-token';
- process.env.TRPC_URL = 'https://trpc.example.com';
+ process.env.R_TRPC_URL = 'https://trpc.example.com';
process.env.ROOMOTE_WORKSPACE_PATH = '/workspace';
expect(getArtifactConfig()).toEqual({
diff --git a/apps/worker/src/mcp/roomote-mcp-server/config.ts b/apps/worker/src/mcp/roomote-mcp-server/config.ts
index a866a7b5..8345b2aa 100644
--- a/apps/worker/src/mcp/roomote-mcp-server/config.ts
+++ b/apps/worker/src/mcp/roomote-mcp-server/config.ts
@@ -7,7 +7,7 @@ function normalizeApiBaseUrl(url: string): string {
function resolvePlatformApiUrl(): string {
return normalizeApiBaseUrl(
process.env.ROOMOTE_PLATFORM_API_URL ||
- process.env.TRPC_URL ||
+ process.env.R_TRPC_URL ||
'http://localhost:13001',
);
}
diff --git a/apps/worker/src/monitoring/sentry.test.ts b/apps/worker/src/monitoring/sentry.test.ts
index 8609e8f8..1d07216a 100644
--- a/apps/worker/src/monitoring/sentry.test.ts
+++ b/apps/worker/src/monitoring/sentry.test.ts
@@ -78,7 +78,7 @@ describe('worker sentry monitoring', () => {
delete process.env.APP_ENV;
delete process.env.NODE_ENV;
delete process.env.WORKER_RELEASE_TAG;
- delete process.env.WORKER_RELEASE_CHANNEL;
+ delete process.env.R_WORKER_RELEASE_CHANNEL;
delete process.env.VERCEL_GIT_COMMIT_SHA;
delete process.env.GITHUB_SHA;
delete process.env.RELEASE_VERSION;
diff --git a/apps/worker/src/monitoring/worker-release-metadata.ts b/apps/worker/src/monitoring/worker-release-metadata.ts
index 72586484..91e86a0e 100644
--- a/apps/worker/src/monitoring/worker-release-metadata.ts
+++ b/apps/worker/src/monitoring/worker-release-metadata.ts
@@ -47,7 +47,7 @@ function parseWorkerVersionFromReleaseTag(
function resolveWorkerReleaseChannel(
env: NodeJS.ProcessEnv,
): WorkerReleaseChannel {
- const explicitChannel = env.WORKER_RELEASE_CHANNEL?.trim();
+ const explicitChannel = env.R_WORKER_RELEASE_CHANNEL?.trim();
if (explicitChannel === 'preview' || explicitChannel === 'stable') {
return explicitChannel;
diff --git a/apps/worker/src/run-task/env.ts b/apps/worker/src/run-task/env.ts
index 8c14aa17..ca2accdb 100644
--- a/apps/worker/src/run-task/env.ts
+++ b/apps/worker/src/run-task/env.ts
@@ -9,13 +9,13 @@ import { ALLOWED_ENV_VARS } from './constants';
const ALLOWED_ENV_PREFIXES = ['MISE_'];
const BLOCKED_HARNESS_ENV_KEYS = new Set([
'AUTH_TOKEN',
- 'TRPC_URL',
+ 'R_TRPC_URL',
'R_APP_URL',
'JOB_AUTH_PRIVATE_KEY',
'JOB_AUTH_PUBLIC_KEY',
'PREVIEW_AUTH_PUBLIC_KEY',
'PREVIEW_AUTH_COOKIE_NAME',
- 'PREVIEW_PROXY_BASE_URL',
+ 'R_PREVIEW_PROXY_BASE_URL',
'PREVIEW_PROXY_SUBDOMAIN_SUFFIX',
]);
const MODEL_RUNTIME_ENV_KEYS = [
diff --git a/apps/worker/src/run-task/polling/cancel.test.ts b/apps/worker/src/run-task/polling/cancel.test.ts
index d2dbab73..06681348 100644
--- a/apps/worker/src/run-task/polling/cancel.test.ts
+++ b/apps/worker/src/run-task/polling/cancel.test.ts
@@ -74,14 +74,14 @@ function createListenerOptions(overrides?: {
}
describe('createCancelInterval', () => {
- const originalTrpcUrl = process.env.TRPC_URL;
+ const originalTrpcUrl = process.env.R_TRPC_URL;
const originalAuthToken = process.env.AUTH_TOKEN;
const originalBypassValue = process.env.ROOMOTE_AUTH_BYPASS_VALUE;
beforeEach(() => {
vi.useFakeTimers();
vi.clearAllMocks();
- process.env.TRPC_URL = 'http://127.0.0.1:3001';
+ process.env.R_TRPC_URL = 'http://127.0.0.1:3001';
process.env.AUTH_TOKEN = 'worker-auth-token';
process.env.ROOMOTE_AUTH_BYPASS_VALUE = 'bypass-token';
});
@@ -90,9 +90,9 @@ describe('createCancelInterval', () => {
vi.useRealTimers();
if (originalTrpcUrl === undefined) {
- delete process.env.TRPC_URL;
+ delete process.env.R_TRPC_URL;
} else {
- process.env.TRPC_URL = originalTrpcUrl;
+ process.env.R_TRPC_URL = originalTrpcUrl;
}
if (originalAuthToken === undefined) {
diff --git a/apps/worker/src/run-task/polling/linear.test.ts b/apps/worker/src/run-task/polling/linear.test.ts
index d49ef335..95e2bed8 100644
--- a/apps/worker/src/run-task/polling/linear.test.ts
+++ b/apps/worker/src/run-task/polling/linear.test.ts
@@ -124,7 +124,7 @@ function createListenerOptions(overrides?: {
}
describe('createLinearMessageInterval', () => {
- const originalTrpcUrl = process.env.TRPC_URL;
+ const originalTrpcUrl = process.env.R_TRPC_URL;
const originalAuthToken = process.env.AUTH_TOKEN;
const originalBypassValue = process.env.ROOMOTE_AUTH_BYPASS_VALUE;
@@ -142,7 +142,7 @@ describe('createLinearMessageInterval', () => {
);
mockPrependLinearMessages.mockResolvedValue(undefined);
mockPrependLinearRequestUserInputAnswers.mockResolvedValue(undefined);
- process.env.TRPC_URL = 'http://127.0.0.1:3001';
+ process.env.R_TRPC_URL = 'http://127.0.0.1:3001';
process.env.AUTH_TOKEN = 'worker-auth-token';
process.env.ROOMOTE_AUTH_BYPASS_VALUE = 'bypass-token';
});
@@ -151,9 +151,9 @@ describe('createLinearMessageInterval', () => {
vi.useRealTimers();
if (originalTrpcUrl === undefined) {
- delete process.env.TRPC_URL;
+ delete process.env.R_TRPC_URL;
} else {
- process.env.TRPC_URL = originalTrpcUrl;
+ process.env.R_TRPC_URL = originalTrpcUrl;
}
if (originalAuthToken === undefined) {
diff --git a/apps/worker/src/run-task/polling/poll-error-context.ts b/apps/worker/src/run-task/polling/poll-error-context.ts
index 626013e3..8b685475 100644
--- a/apps/worker/src/run-task/polling/poll-error-context.ts
+++ b/apps/worker/src/run-task/polling/poll-error-context.ts
@@ -40,7 +40,7 @@ function buildPollingTransportErrorContext({
string,
string | number | boolean | null
> {
- const trpcUrl = env.TRPC_URL?.trim() || null;
+ const trpcUrl = env.R_TRPC_URL?.trim() || null;
try {
const parsedTrpcUrl = trpcUrl ? new URL(trpcUrl) : null;
diff --git a/apps/worker/src/run-task/polling/slack.test.ts b/apps/worker/src/run-task/polling/slack.test.ts
index 947f6007..a7d1f0e2 100644
--- a/apps/worker/src/run-task/polling/slack.test.ts
+++ b/apps/worker/src/run-task/polling/slack.test.ts
@@ -139,7 +139,7 @@ function createListenerOptions(overrides?: {
}
describe('createSlackMessageInterval', () => {
- const originalTrpcUrl = process.env.TRPC_URL;
+ const originalTrpcUrl = process.env.R_TRPC_URL;
const originalAuthToken = process.env.AUTH_TOKEN;
const originalBypassValue = process.env.ROOMOTE_AUTH_BYPASS_VALUE;
const tempDirs: string[] = [];
@@ -156,7 +156,7 @@ describe('createSlackMessageInterval', () => {
);
mockPrependSlackMessages.mockResolvedValue(undefined);
mockPrependSlackRequestUserInputAnswers.mockResolvedValue(undefined);
- process.env.TRPC_URL = 'http://127.0.0.1:3001';
+ process.env.R_TRPC_URL = 'http://127.0.0.1:3001';
process.env.AUTH_TOKEN = 'worker-auth-token';
process.env.ROOMOTE_AUTH_BYPASS_VALUE = 'bypass-token';
});
@@ -169,9 +169,9 @@ describe('createSlackMessageInterval', () => {
}
if (originalTrpcUrl === undefined) {
- delete process.env.TRPC_URL;
+ delete process.env.R_TRPC_URL;
} else {
- process.env.TRPC_URL = originalTrpcUrl;
+ process.env.R_TRPC_URL = originalTrpcUrl;
}
if (originalAuthToken === undefined) {
diff --git a/deploy/README.md b/deploy/README.md
index e03167d7..d737ef4f 100644
--- a/deploy/README.md
+++ b/deploy/README.md
@@ -88,17 +88,17 @@ chmod 600 /opt/roomote/.env
```
Deployment-owned image metadata includes the control-plane image tag, worker
-image, and the controller-local `DOCKER_WORKER_RELEASE_PATH`. The release path is
+image, and the controller-local `R_DOCKER_WORKER_RELEASE_PATH`. The release path is
versioned as `/roomote/releases/worker-v.tar.gz` so hosted providers
such as Modal can derive worker release metadata from the archive filename.
The installer and deployer also keep `MODAL_BASE_IMAGE_REF` aligned with
-`DOCKER_WORKER_IMAGE` if the Modal image is blank or still points at the
+`R_DOCKER_WORKER_IMAGE` if the Modal image is blank or still points at the
previously deployed worker image, so picking Modal in the setup wizard only
requires the Modal token pair. A different non-empty Modal image is treated
as an explicit operator override.
Both worker-image variables are optional on published app images:
-when `DOCKER_WORKER_IMAGE` (and, for Modal, `MODAL_BASE_IMAGE_REF`) are
+when `R_DOCKER_WORKER_IMAGE` (and, for Modal, `MODAL_BASE_IMAGE_REF`) are
unset, the app derives `:${RELEASE_VERSION}` from the release
version baked into the app image — the publish workflow tags the app and
worker images in lockstep. The worker repository defaults to
@@ -191,18 +191,24 @@ base64.
Operator-facing environment variables follow three naming categories:
1. **`R_*` for Roomote-owned configuration.** Anything Roomote itself defines
- and an operator is expected to set — app URLs, model role overrides,
- Roomote's own registered integration-app credentials (GitHub App, Slack app,
- Microsoft/Teams app, Telegram bot, Linear app), allowed emails, and boot
- knobs such as `R_AUTO_GENERATE_KEYS`.
+ and an operator is expected to set — app URLs (`R_APP_URL`, `R_TRPC_URL`,
+ `R_PREVIEW_*`), compute selection and worker/policy knobs
+ (`R_DEFAULT_COMPUTE_PROVIDER`, `R_DOCKER_*`, `R_BLAXEL_STANDBY_*`), model
+ role overrides, Roomote-registered integration/app credentials (GitHub App,
+ Slack app, Microsoft/Teams app, Telegram bot, Linear app, plus non-token
+ GitLab/Gitea/ADO/Bitbucket app settings), allowed emails, and boot knobs
+ such as `R_AUTO_GENERATE_KEYS`.
2. **Conventional infrastructure names stay conventional.** `DATABASE_URL`,
- `REDIS_URL`, `S3_*`, `NODE_ENV`, `HOST`, `PORT`, and similar
- ecosystem-standard names are not prefixed; tooling and platforms expect
- them as-is.
+ `REDIS_URL`, `S3_*`, `NODE_ENV`, `HOST`, `PORT`, signing keypairs
+ (`JOB_AUTH_*`, `PREVIEW_AUTH_*`, `ENCRYPTION_KEY`, …), and similar
+ ecosystem-standard or operational secret names are not prefixed; tooling
+ and platforms expect them as-is.
3. **Third-party provider credentials keep the provider's standard names.**
- `MODAL_*`, `E2B_*`, `DAYTONA_*`, `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, and
- other keys issued by an external service keep the names that service's own
- documentation uses.
+ Compute SDK credentials (`MODAL_*`, `E2B_*`, `DAYTONA_*`, `BL_*`),
+ per-repo source-control access tokens (`GITLAB_TOKEN`, `GITEA_TOKEN`, …),
+ model API keys (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, …), and other keys
+ issued by an external service keep the names that service's own
+ documentation or sandbox tooling uses.
**Internal-plumbing carve-out:** variables Roomote sets for its own processes
— and that operators never configure — are not part of the naming contract and
@@ -322,8 +328,8 @@ running:
cd /opt/roomote
docker compose --env-file .env -f docker-compose.prod.yml config >/dev/null
docker compose --env-file .env -f docker-compose.prod.yml stop controller || true
-DOCKER_WORKER_IMAGE="$(awk -F= '/^(export[[:space:]]+)?DOCKER_WORKER_IMAGE=/ { sub(/^[^=]*=/, ""); print; exit }' .env)"
-docker pull "$DOCKER_WORKER_IMAGE"
+R_DOCKER_WORKER_IMAGE="$(awk -F= '/^(export[[:space:]]+)?R_DOCKER_WORKER_IMAGE=/ { sub(/^[^=]*=/, ""); print; exit }' .env)"
+docker pull "$R_DOCKER_WORKER_IMAGE"
docker compose --env-file .env -f docker-compose.prod.yml pull
docker compose --env-file .env -f docker-compose.prod.yml run --rm db-migrate
docker compose --env-file .env -f docker-compose.prod.yml up -d --wait --wait-timeout 600
diff --git a/deploy/ci/deployment-smoke.sh b/deploy/ci/deployment-smoke.sh
index c9abb3c6..c357b835 100755
--- a/deploy/ci/deployment-smoke.sh
+++ b/deploy/ci/deployment-smoke.sh
@@ -8,7 +8,7 @@ project_name="${COMPOSE_PROJECT_NAME:-roomote-deployment-ci}"
postgres_port="${DEPLOYMENT_CI_POSTGRES_PORT:-55432}"
redis_port="${DEPLOYMENT_CI_REDIS_PORT:-56379}"
default_network="${ROOMOTE_DEFAULT_NETWORK:-${project_name}_default}"
-worker_network="${DOCKER_WORKER_NETWORK:-${project_name}_worker}"
+worker_network="${R_DOCKER_WORKER_NETWORK:-${project_name}_worker}"
temporary_directory="$(mktemp -d)"
env_file="$temporary_directory/deployment.env"
backup_file="$temporary_directory/roomote.sql"
@@ -106,17 +106,17 @@ CADDY_HTTPS_PORT=18443
COMPOSE_PROFILES=local-postgres
DASHBOARD_PASSWORD=$dashboard_password
DATABASE_URL=postgres://postgres:roomote-postgres-password@postgres:5432/roomote
-DEFAULT_COMPUTE_PROVIDER=docker
+R_DEFAULT_COMPUTE_PROVIDER=docker
DEPLOYMENT_CI_POSTGRES_PORT=$postgres_port
DEPLOYMENT_CI_REDIS_PORT=$redis_port
# CI runners are ephemeral and their Docker storage drivers cannot enforce
# --storage-opt size quotas, so accept unbounded writable layers here. Real
# deployments keep the fail-closed default.
-DOCKER_WORKER_ALLOW_UNBOUNDED_DISK=true
-DOCKER_WORKER_IMAGE=localhost/roomote/roomote-worker:$version
-DOCKER_WORKER_NETWORK=$worker_network
-DOCKER_WORKER_PLATFORM=$platform
-DOCKER_WORKER_RELEASE_PATH=/roomote/releases/worker-current.tar.gz
+R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK=true
+R_DOCKER_WORKER_IMAGE=localhost/roomote/roomote-worker:$version
+R_DOCKER_WORKER_NETWORK=$worker_network
+R_DOCKER_WORKER_PLATFORM=$platform
+R_DOCKER_WORKER_RELEASE_PATH=/roomote/releases/worker-current.tar.gz
ENCRYPTION_KEY=$encryption_key
IMAGE_NAMESPACE=roomote
IMAGE_REGISTRY=localhost
@@ -141,7 +141,7 @@ S3_PRESIGN_ENDPOINT=http://minio:9000
S3_REGION=us-east-1
S3_SECRET_ACCESS_KEY=$s3_password
SETUP_TOKEN=$setup_token
-TRPC_URL=http://api:3001
+R_TRPC_URL=http://api:3001
EOF
}
diff --git a/deploy/ci/docker-compose.ci.yml b/deploy/ci/docker-compose.ci.yml
index ca927638..bb4b7447 100644
--- a/deploy/ci/docker-compose.ci.yml
+++ b/deploy/ci/docker-compose.ci.yml
@@ -42,4 +42,4 @@ networks:
default:
name: ${ROOMOTE_DEFAULT_NETWORK:-roomote_ci_default}
worker:
- name: ${DOCKER_WORKER_NETWORK:-roomote_ci_worker}
+ name: ${R_DOCKER_WORKER_NETWORK:-roomote_ci_worker}
diff --git a/deploy/ci/upgrade-compatibility.sh b/deploy/ci/upgrade-compatibility.sh
index 212c8295..2e135060 100755
--- a/deploy/ci/upgrade-compatibility.sh
+++ b/deploy/ci/upgrade-compatibility.sh
@@ -26,7 +26,7 @@ project_name="${COMPOSE_PROJECT_NAME:-roomote-upgrade-ci}"
postgres_port="${DEPLOYMENT_CI_POSTGRES_PORT:-57432}"
redis_port="${DEPLOYMENT_CI_REDIS_PORT:-58379}"
default_network="${ROOMOTE_DEFAULT_NETWORK:-${project_name}_default}"
-worker_network="${DOCKER_WORKER_NETWORK:-${project_name}_worker}"
+worker_network="${R_DOCKER_WORKER_NETWORK:-${project_name}_worker}"
temporary_directory="$(mktemp -d)"
env_file="$temporary_directory/deployment.env"
@@ -124,13 +124,13 @@ CADDY_HTTPS_PORT=19443
COMPOSE_PROFILES=local-postgres
DASHBOARD_PASSWORD=$dashboard_password
DATABASE_URL=postgres://postgres:roomote-postgres-password@postgres:5432/roomote
-DEFAULT_COMPUTE_PROVIDER=docker
+R_DEFAULT_COMPUTE_PROVIDER=docker
DEPLOYMENT_CI_POSTGRES_PORT=$postgres_port
DEPLOYMENT_CI_REDIS_PORT=$redis_port
-DOCKER_WORKER_IMAGE=$worker_image
-DOCKER_WORKER_NETWORK=$worker_network
-DOCKER_WORKER_PLATFORM=$platform
-DOCKER_WORKER_RELEASE_PATH=/roomote/releases/worker-current.tar.gz
+R_DOCKER_WORKER_IMAGE=$worker_image
+R_DOCKER_WORKER_NETWORK=$worker_network
+R_DOCKER_WORKER_PLATFORM=$platform
+R_DOCKER_WORKER_RELEASE_PATH=/roomote/releases/worker-current.tar.gz
ENCRYPTION_KEY=$encryption_key
IMAGE_NAMESPACE=$baseline_namespace
IMAGE_REGISTRY=$baseline_registry
@@ -155,7 +155,7 @@ S3_PRESIGN_ENDPOINT=http://minio:9000
S3_REGION=us-east-1
S3_SECRET_ACCESS_KEY=$s3_password
SETUP_TOKEN=$setup_token
-TRPC_URL=http://api:3001
+R_TRPC_URL=http://api:3001
EOF
verify_endpoints() {
diff --git a/deploy/ci/validate-deployment-artifacts.mjs b/deploy/ci/validate-deployment-artifacts.mjs
index c0fe8e1e..09a7f33c 100644
--- a/deploy/ci/validate-deployment-artifacts.mjs
+++ b/deploy/ci/validate-deployment-artifacts.mjs
@@ -38,8 +38,8 @@ const composeEnv = {
COMPOSE_PROFILES: 'local-postgres',
DASHBOARD_PASSWORD: 'deployment-ci-dashboard-password',
DATABASE_URL: 'postgres://postgres:password@postgres:5432/roomote',
- DEFAULT_COMPUTE_PROVIDER: 'docker',
- DOCKER_WORKER_IMAGE: 'roomote-worker:deployment-ci',
+ R_DEFAULT_COMPUTE_PROVIDER: 'docker',
+ R_DOCKER_WORKER_IMAGE: 'roomote-worker:deployment-ci',
ENCRYPTION_KEY: 'deployment-ci-encryption-key',
IMAGE_NAMESPACE: 'roomote',
IMAGE_REGISTRY: 'localhost',
diff --git a/deploy/compose/docker-compose.prod.yml b/deploy/compose/docker-compose.prod.yml
index 48af02a6..c5e42d99 100644
--- a/deploy/compose/docker-compose.prod.yml
+++ b/deploy/compose/docker-compose.prod.yml
@@ -20,7 +20,7 @@ x-roomote-base-env: &roomote-base-env
ROOMOTE_APP_URL: https://${ROOMOTE_APP_DOMAIN:?ROOMOTE_APP_DOMAIN is required}
R_PUBLIC_URL: https://${ROOMOTE_APP_DOMAIN:?ROOMOTE_APP_DOMAIN is required}
ROOMOTE_PUBLIC_URL: https://${ROOMOTE_APP_DOMAIN:?ROOMOTE_APP_DOMAIN is required}
- TRPC_URL: ${TRPC_URL:-https://${ROOMOTE_APP_DOMAIN:?ROOMOTE_APP_DOMAIN is required}/_roomote-api}
+ R_TRPC_URL: ${R_TRPC_URL:-https://${ROOMOTE_APP_DOMAIN:?ROOMOTE_APP_DOMAIN is required}/_roomote-api}
x-roomote-inference-env: &roomote-inference-env
<<: *roomote-base-env
@@ -67,9 +67,9 @@ x-roomote-web-env: &roomote-web-env
ARTIFACT_SIGNING_KEY: ${ARTIFACT_SIGNING_KEY:?ARTIFACT_SIGNING_KEY is required}
ARTIFACT_SIGNING_KEY_PREVIOUS: ${ARTIFACT_SIGNING_KEY_PREVIOUS:-}
DASHBOARD_PASSWORD: ${DASHBOARD_PASSWORD:?DASHBOARD_PASSWORD is required}
- PREVIEW_PROXY_BASE_URL: https://${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
+ R_PREVIEW_PROXY_BASE_URL: https://${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
NEXT_PUBLIC_PREVIEW_PROXY_BASE_URL: https://${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
- PREVIEW_DOMAINS: ${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
+ R_PREVIEW_DOMAINS: ${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
R_ALLOWED_EMAILS: ${R_ALLOWED_EMAILS:-}
R_GITHUB_APP_SLUG: ${R_GITHUB_APP_SLUG:-}
SETUP_TOKEN: ${SETUP_TOKEN:-}
@@ -79,9 +79,9 @@ x-roomote-web-env: &roomote-web-env
R_GITHUB_CLIENT_SECRET: ${R_GITHUB_CLIENT_SECRET:-}
R_GITHUB_WEBHOOK_SECRET: ${R_GITHUB_WEBHOOK_SECRET:-}
GITLAB_TOKEN: ${GITLAB_TOKEN:-}
- GITLAB_WEBHOOK_SECRET: ${GITLAB_WEBHOOK_SECRET:-}
- GITLAB_WEBHOOK_SIGNING_TOKEN: ${GITLAB_WEBHOOK_SIGNING_TOKEN:-}
- SLACK_APP_ID: ${SLACK_APP_ID:-}
+ R_GITLAB_WEBHOOK_SECRET: ${R_GITLAB_WEBHOOK_SECRET:-}
+ R_GITLAB_WEBHOOK_SIGNING_TOKEN: ${R_GITLAB_WEBHOOK_SIGNING_TOKEN:-}
+ R_SLACK_APP_ID: ${R_SLACK_APP_ID:-}
R_SLACK_CLIENT_ID: ${R_SLACK_CLIENT_ID:-}
R_SLACK_CLIENT_SECRET: ${R_SLACK_CLIENT_SECRET:-}
R_SLACK_SIGNING_SECRET: ${R_SLACK_SIGNING_SECRET:-}
@@ -90,23 +90,23 @@ x-roomote-web-env: &roomote-web-env
R_LINEAR_CLIENT_ID: ${R_LINEAR_CLIENT_ID:-}
R_LINEAR_CLIENT_SECRET: ${R_LINEAR_CLIENT_SECRET:-}
R_LINEAR_WEBHOOK_SECRET: ${R_LINEAR_WEBHOOK_SECRET:-}
- DEFAULT_COMPUTE_PROVIDER: ${DEFAULT_COMPUTE_PROVIDER:-docker}
+ R_DEFAULT_COMPUTE_PROVIDER: ${R_DEFAULT_COMPUTE_PROVIDER:-docker}
# Optional: when unset, the app derives the published worker image from the
# RELEASE_VERSION baked into the app image (same tag on both images), using
# the repository below so registry mirrors stay consistent.
- DOCKER_WORKER_IMAGE: ${DOCKER_WORKER_IMAGE:-}
+ R_DOCKER_WORKER_IMAGE: ${R_DOCKER_WORKER_IMAGE:-}
ROOMOTE_WORKER_IMAGE_REPO: ${ROOMOTE_WORKER_IMAGE_REPO:-${IMAGE_REGISTRY:-ghcr.io}/${IMAGE_NAMESPACE:-roocodeinc}/roomote-worker}
- DOCKER_WORKER_PLATFORM: ${DOCKER_WORKER_PLATFORM:-linux/amd64}
- DOCKER_WORKER_NETWORK: ${DOCKER_WORKER_NETWORK:-roomote_worker}
- DOCKER_WORKER_RELEASE_PATH: ${DOCKER_WORKER_RELEASE_PATH:-/roomote/releases/worker-current.tar.gz}
- DOCKER_WORKER_CPU_LIMIT: ${DOCKER_WORKER_CPU_LIMIT:-2}
- DOCKER_WORKER_MEMORY_LIMIT: ${DOCKER_WORKER_MEMORY_LIMIT:-4g}
- DOCKER_WORKER_PIDS_LIMIT: ${DOCKER_WORKER_PIDS_LIMIT:-512}
- DOCKER_WORKER_DISK_LIMIT: ${DOCKER_WORKER_DISK_LIMIT:-20g}
- DOCKER_WORKER_ALLOW_UNBOUNDED_DISK: ${DOCKER_WORKER_ALLOW_UNBOUNDED_DISK:-false}
- DOCKER_WORKER_LOG_MAX_SIZE: ${DOCKER_WORKER_LOG_MAX_SIZE:-10m}
- DOCKER_WORKER_LOG_MAX_FILES: ${DOCKER_WORKER_LOG_MAX_FILES:-3}
- DOCKER_WORKER_EGRESS_POLICY: ${DOCKER_WORKER_EGRESS_POLICY:-internet}
+ R_DOCKER_WORKER_PLATFORM: ${R_DOCKER_WORKER_PLATFORM:-linux/amd64}
+ R_DOCKER_WORKER_NETWORK: ${R_DOCKER_WORKER_NETWORK:-roomote_worker}
+ R_DOCKER_WORKER_RELEASE_PATH: ${R_DOCKER_WORKER_RELEASE_PATH:-/roomote/releases/worker-current.tar.gz}
+ R_DOCKER_WORKER_CPU_LIMIT: ${R_DOCKER_WORKER_CPU_LIMIT:-2}
+ R_DOCKER_WORKER_MEMORY_LIMIT: ${R_DOCKER_WORKER_MEMORY_LIMIT:-4g}
+ R_DOCKER_WORKER_PIDS_LIMIT: ${R_DOCKER_WORKER_PIDS_LIMIT:-512}
+ R_DOCKER_WORKER_DISK_LIMIT: ${R_DOCKER_WORKER_DISK_LIMIT:-20g}
+ R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK: ${R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK:-false}
+ R_DOCKER_WORKER_LOG_MAX_SIZE: ${R_DOCKER_WORKER_LOG_MAX_SIZE:-10m}
+ R_DOCKER_WORKER_LOG_MAX_FILES: ${R_DOCKER_WORKER_LOG_MAX_FILES:-3}
+ R_DOCKER_WORKER_EGRESS_POLICY: ${R_DOCKER_WORKER_EGRESS_POLICY:-internet}
MODAL_TOKEN_ID: ${MODAL_TOKEN_ID:-}
MODAL_TOKEN_SECRET: ${MODAL_TOKEN_SECRET:-}
MODAL_ENDPOINT: ${MODAL_ENDPOINT:-}
@@ -135,8 +135,8 @@ x-roomote-api-env: &roomote-api-env
ARTIFACT_SIGNING_KEY: ${ARTIFACT_SIGNING_KEY:?ARTIFACT_SIGNING_KEY is required}
ARTIFACT_SIGNING_KEY_PREVIOUS: ${ARTIFACT_SIGNING_KEY_PREVIOUS:-}
DASHBOARD_PASSWORD: ${DASHBOARD_PASSWORD:?DASHBOARD_PASSWORD is required}
- PREVIEW_PROXY_BASE_URL: https://${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
- PREVIEW_DOMAINS: ${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
+ R_PREVIEW_PROXY_BASE_URL: https://${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
+ R_PREVIEW_DOMAINS: ${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
R_GITHUB_APP_SLUG: ${R_GITHUB_APP_SLUG:-}
R_GITHUB_APP_ID: ${R_GITHUB_APP_ID:-}
R_GITHUB_APP_PRIVATE_KEY: ${R_GITHUB_APP_PRIVATE_KEY:-}
@@ -144,9 +144,9 @@ x-roomote-api-env: &roomote-api-env
R_GITHUB_CLIENT_SECRET: ${R_GITHUB_CLIENT_SECRET:-}
R_GITHUB_WEBHOOK_SECRET: ${R_GITHUB_WEBHOOK_SECRET:-}
GITLAB_TOKEN: ${GITLAB_TOKEN:-}
- GITLAB_WEBHOOK_SECRET: ${GITLAB_WEBHOOK_SECRET:-}
- GITLAB_WEBHOOK_SIGNING_TOKEN: ${GITLAB_WEBHOOK_SIGNING_TOKEN:-}
- SLACK_APP_ID: ${SLACK_APP_ID:-}
+ R_GITLAB_WEBHOOK_SECRET: ${R_GITLAB_WEBHOOK_SECRET:-}
+ R_GITLAB_WEBHOOK_SIGNING_TOKEN: ${R_GITLAB_WEBHOOK_SIGNING_TOKEN:-}
+ R_SLACK_APP_ID: ${R_SLACK_APP_ID:-}
R_SLACK_CLIENT_ID: ${R_SLACK_CLIENT_ID:-}
R_SLACK_CLIENT_SECRET: ${R_SLACK_CLIENT_SECRET:-}
R_SLACK_SIGNING_SECRET: ${R_SLACK_SIGNING_SECRET:-}
@@ -166,23 +166,23 @@ x-roomote-controller-env: &roomote-controller-env
JOB_AUTH_PRIVATE_KEY: ${JOB_AUTH_PRIVATE_KEY:?JOB_AUTH_PRIVATE_KEY is required}
JOB_AUTH_PUBLIC_KEY: ${JOB_AUTH_PUBLIC_KEY:?JOB_AUTH_PUBLIC_KEY is required}
ENCRYPTION_KEY: ${ENCRYPTION_KEY:?ENCRYPTION_KEY is required}
- PREVIEW_PROXY_BASE_URL: https://${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
- PREVIEW_DOMAINS: ${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
- DEFAULT_COMPUTE_PROVIDER: ${DEFAULT_COMPUTE_PROVIDER:-docker}
+ R_PREVIEW_PROXY_BASE_URL: https://${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
+ R_PREVIEW_DOMAINS: ${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
+ R_DEFAULT_COMPUTE_PROVIDER: ${R_DEFAULT_COMPUTE_PROVIDER:-docker}
DOCKER_HOST: tcp://docker-proxy:2375
- DOCKER_WORKER_IMAGE: ${DOCKER_WORKER_IMAGE:-}
+ R_DOCKER_WORKER_IMAGE: ${R_DOCKER_WORKER_IMAGE:-}
ROOMOTE_WORKER_IMAGE_REPO: ${ROOMOTE_WORKER_IMAGE_REPO:-${IMAGE_REGISTRY:-ghcr.io}/${IMAGE_NAMESPACE:-roocodeinc}/roomote-worker}
- DOCKER_WORKER_PLATFORM: ${DOCKER_WORKER_PLATFORM:-linux/amd64}
- DOCKER_WORKER_NETWORK: ${DOCKER_WORKER_NETWORK:-roomote_worker}
- DOCKER_WORKER_RELEASE_PATH: ${DOCKER_WORKER_RELEASE_PATH:-/roomote/releases/worker-current.tar.gz}
- DOCKER_WORKER_CPU_LIMIT: ${DOCKER_WORKER_CPU_LIMIT:-2}
- DOCKER_WORKER_MEMORY_LIMIT: ${DOCKER_WORKER_MEMORY_LIMIT:-4g}
- DOCKER_WORKER_PIDS_LIMIT: ${DOCKER_WORKER_PIDS_LIMIT:-512}
- DOCKER_WORKER_DISK_LIMIT: ${DOCKER_WORKER_DISK_LIMIT:-20g}
- DOCKER_WORKER_ALLOW_UNBOUNDED_DISK: ${DOCKER_WORKER_ALLOW_UNBOUNDED_DISK:-false}
- DOCKER_WORKER_LOG_MAX_SIZE: ${DOCKER_WORKER_LOG_MAX_SIZE:-10m}
- DOCKER_WORKER_LOG_MAX_FILES: ${DOCKER_WORKER_LOG_MAX_FILES:-3}
- DOCKER_WORKER_EGRESS_POLICY: ${DOCKER_WORKER_EGRESS_POLICY:-internet}
+ R_DOCKER_WORKER_PLATFORM: ${R_DOCKER_WORKER_PLATFORM:-linux/amd64}
+ R_DOCKER_WORKER_NETWORK: ${R_DOCKER_WORKER_NETWORK:-roomote_worker}
+ R_DOCKER_WORKER_RELEASE_PATH: ${R_DOCKER_WORKER_RELEASE_PATH:-/roomote/releases/worker-current.tar.gz}
+ R_DOCKER_WORKER_CPU_LIMIT: ${R_DOCKER_WORKER_CPU_LIMIT:-2}
+ R_DOCKER_WORKER_MEMORY_LIMIT: ${R_DOCKER_WORKER_MEMORY_LIMIT:-4g}
+ R_DOCKER_WORKER_PIDS_LIMIT: ${R_DOCKER_WORKER_PIDS_LIMIT:-512}
+ R_DOCKER_WORKER_DISK_LIMIT: ${R_DOCKER_WORKER_DISK_LIMIT:-20g}
+ R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK: ${R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK:-false}
+ R_DOCKER_WORKER_LOG_MAX_SIZE: ${R_DOCKER_WORKER_LOG_MAX_SIZE:-10m}
+ R_DOCKER_WORKER_LOG_MAX_FILES: ${R_DOCKER_WORKER_LOG_MAX_FILES:-3}
+ R_DOCKER_WORKER_EGRESS_POLICY: ${R_DOCKER_WORKER_EGRESS_POLICY:-internet}
MODAL_TOKEN_ID: ${MODAL_TOKEN_ID:-}
MODAL_TOKEN_SECRET: ${MODAL_TOKEN_SECRET:-}
MODAL_ENDPOINT: ${MODAL_ENDPOINT:-}
@@ -201,7 +201,7 @@ x-roomote-bullmq-env: &roomote-bullmq-env
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
DASHBOARD_PASSWORD: ${DASHBOARD_PASSWORD:?DASHBOARD_PASSWORD is required}
ENCRYPTION_KEY: ${ENCRYPTION_KEY:?ENCRYPTION_KEY is required}
- DEFAULT_COMPUTE_PROVIDER: ${DEFAULT_COMPUTE_PROVIDER:-docker}
+ R_DEFAULT_COMPUTE_PROVIDER: ${R_DEFAULT_COMPUTE_PROVIDER:-docker}
R_TELEGRAM_BOT_TOKEN: ${R_TELEGRAM_BOT_TOKEN:-}
MODAL_TOKEN_ID: ${MODAL_TOKEN_ID:-}
MODAL_TOKEN_SECRET: ${MODAL_TOKEN_SECRET:-}
@@ -221,8 +221,8 @@ x-roomote-preview-proxy-env: &roomote-preview-proxy-env
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
JOB_AUTH_PUBLIC_KEY: ${JOB_AUTH_PUBLIC_KEY:?JOB_AUTH_PUBLIC_KEY is required}
PREVIEW_AUTH_PUBLIC_KEY: ${PREVIEW_AUTH_PUBLIC_KEY:?PREVIEW_AUTH_PUBLIC_KEY is required}
- PREVIEW_PROXY_BASE_URL: https://${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
- PREVIEW_DOMAINS: ${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
+ R_PREVIEW_PROXY_BASE_URL: https://${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
+ R_PREVIEW_DOMAINS: ${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
x-roomote-service: &roomote-service
restart: unless-stopped
@@ -360,7 +360,7 @@ services:
<<: *roomote-service
image: *roomote-app-image
command: ['api']
- # Reachable from task worker containers (worker -> API over TRPC_URL).
+ # Reachable from task worker containers (worker -> API over R_TRPC_URL).
networks: [default, worker]
depends_on:
redis:
diff --git a/deploy/coolify/README.md b/deploy/coolify/README.md
index 605bea90..609a1336 100644
--- a/deploy/coolify/README.md
+++ b/deploy/coolify/README.md
@@ -16,7 +16,7 @@ instead. For a managed PaaS with no server of your own, see
- **Coolify's proxy is the HTTPS edge.** There is no bundled Caddy and no
`/_roomote-api` path routing. The web app, the API, and MinIO each get
their own public domain through `SERVICE_FQDN_*` declarations in the
- compose file, so `TRPC_URL` points at the api service's domain directly.
+ compose file, so `R_TRPC_URL` points at the api service's domain directly.
GitHub webhooks and sandbox workers call that API origin, and Slack
webhooks arrive at the web origin and are proxied to the API internally.
- **The Docker socket is available.** Unlike Railway, the Coolify host runs
@@ -69,7 +69,7 @@ registry login is needed.
The template tracks the mutable **`:develop`** alias, which the publish
workflow moves to every new develop build (releases also move `:latest`).
Nothing else in the template encodes a version: the images bake
-`RELEASE_VERSION`, the app derives `DOCKER_WORKER_IMAGE` from it when unset,
+`RELEASE_VERSION`, the app derives `R_DOCKER_WORKER_IMAGE` from it when unset,
and the controller reads the worker release version from the `VERSION` file
inside `worker-current.tar.gz`.
@@ -118,7 +118,7 @@ resource's environment variables panel in Coolify, where you can read
Notes:
-- Leave `DOCKER_WORKER_IMAGE` **unset**. The app derives it from the
+- Leave `R_DOCKER_WORKER_IMAGE` **unset**. The app derives it from the
`RELEASE_VERSION` baked into the running image, so it always matches the
deployed build. Setting it explicitly silently pins the worker to whatever
version the value encodes.
@@ -129,18 +129,18 @@ Notes:
- Leave `JOB_AUTH_*` and `PREVIEW_AUTH_*` unset —
`R_AUTO_GENERATE_KEYS=true` manages them. If you later provide
explicit env values, they take precedence over the persisted keypairs.
-- Leave `PREVIEW_PROXY_BASE_URL` and `PREVIEW_DOMAINS` unset unless you
+- Leave `R_PREVIEW_PROXY_BASE_URL` and `R_PREVIEW_DOMAINS` unset unless you
enable live previews. Roomote boots without them; previews report as not
configured in **Settings → Live Previews** until set.
## Task execution
-The template defaults to `DEFAULT_COMPUTE_PROVIDER=docker`: the controller
+The template defaults to `R_DEFAULT_COMPUTE_PROVIDER=docker`: the controller
mounts the host Docker socket and starts one worker container per task on
the same server. Two things matter in this mode:
- **Worker network.** The controller uses the network named by
- `DOCKER_WORKER_NETWORK` (`roomote_default`, declared at the bottom of the
+ `R_DOCKER_WORKER_NETWORK` (`roomote_default`, declared at the bottom of the
compose file) to discover the labeled API and optional preview-proxy
containers. It creates a separate bridge network for each task and attaches
only the API plus the preview proxy when enabled, so workers do not join
@@ -150,8 +150,8 @@ the same server. Two things matter in this mode:
the standard Compose service labels.
Docker task writable layers fail closed when the host storage driver cannot
- enforce `DOCKER_WORKER_DISK_LIMIT`. Keep
- `DOCKER_WORKER_ALLOW_UNBOUNDED_DISK=false` unless the host already enforces an
+ enforce `R_DOCKER_WORKER_DISK_LIMIT`. Keep
+ `R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK=false` unless the host already enforces an
equivalent quota outside Docker.
- **Trust boundary.** Mounting the Docker socket gives the controller
@@ -161,10 +161,10 @@ the same server. Two things matter in this mode:
To use hosted sandboxes instead (no socket access, task execution bills
through the provider):
-1. Set `DEFAULT_COMPUTE_PROVIDER=modal` (or `e2b` / `daytona`) and add
- `EXCLUDED_COMPUTE_PROVIDERS=docker` to the shared env block.
+1. Set `R_DEFAULT_COMPUTE_PROVIDER=modal` (or `e2b` / `daytona`) and add
+ `R_EXCLUDED_COMPUTE_PROVIDERS=docker` to the shared env block.
2. Remove the `/var/run/docker.sock` volume from the controller service.
-3. Keep `DOCKER_WORKER_RELEASE_PATH` — the controller uploads the baked-in
+3. Keep `R_DOCKER_WORKER_RELEASE_PATH` — the controller uploads the baked-in
worker release archive into hosted sandboxes at spawn time.
4. Enter the provider credentials in the `/setup` wizard after first boot.
@@ -192,7 +192,7 @@ addressing, and `S3_PRESIGN_ENDPOINT` must be reachable from workers.
3. Create the founding admin account (email/password works immediately;
Slack or Microsoft sign-in can be added later).
4. Connect GitHub with **Create GitHub App** — the manifest flow derives the
- callback and webhook URLs from `R_APP_URL` and `TRPC_URL`, so no
+ callback and webhook URLs from `R_APP_URL` and `R_TRPC_URL`, so no
manual URL entry is needed.
5. Enter the model provider key when the wizard asks. With the default
Docker provider there are no sandbox credentials to enter; with hosted
@@ -207,8 +207,8 @@ Live previews need a wildcard domain, which requires DNS you control and a
wildcard-capable TLS setup on the Coolify proxy:
1. Uncomment the `preview-proxy` service in the compose file and set
- `PREVIEW_PROXY_BASE_URL`, `NEXT_PUBLIC_PREVIEW_PROXY_BASE_URL`, and
- `PREVIEW_DOMAINS` to your preview domain. The `NEXT_PUBLIC_` variant must
+ `R_PREVIEW_PROXY_BASE_URL`, `NEXT_PUBLIC_PREVIEW_PROXY_BASE_URL`, and
+ `R_PREVIEW_DOMAINS` to your preview domain. The `NEXT_PUBLIC_` variant must
also be set on the `web` service — the web client builds preview links
from it.
2. Point `previews.` and `*.previews.` at the
diff --git a/deploy/coolify/docker-compose.yaml b/deploy/coolify/docker-compose.yaml
index 50425822..b54c9403 100644
--- a/deploy/coolify/docker-compose.yaml
+++ b/deploy/coolify/docker-compose.yaml
@@ -12,7 +12,7 @@
# which is the only control-plane image containing the Docker CLI.
# - Coolify's proxy is the HTTPS edge. There is no bundled Caddy and no
# `/_roomote-api` path routing: web, api, and minio each get their own
-# public domain through `SERVICE_FQDN_*` declarations, and `TRPC_URL`
+# public domain through `SERVICE_FQDN_*` declarations, and `R_TRPC_URL`
# points at the api origin directly.
# - Coolify magic variables with the same name resolve to the same value
# across every service in this resource, so shared secrets such as
@@ -29,7 +29,7 @@
# Postgres.
# - The template tracks the mutable `:develop` image alias and contains no
# version strings. The images bake RELEASE_VERSION; the app derives
-# `DOCKER_WORKER_IMAGE` from it, and the controller reads the worker
+# `R_DOCKER_WORKER_IMAGE` from it, and the controller reads the worker
# release version from the VERSION file inside `worker-current.tar.gz`.
# Production deployments can pin an immutable `v*` tag in the
# `x-roomote-app-image` anchor.
@@ -56,12 +56,12 @@ x-roomote-shared-env: &roomote-shared-env
S3_REGION: us-east-1
S3_BUCKET_ARTIFACTS: roomote-artifacts
S3_AUTO_CREATE_BUCKET: 'true' # api creates the MinIO bucket at boot
- DEFAULT_COMPUTE_PROVIDER: docker
+ R_DEFAULT_COMPUTE_PROVIDER: docker
DATABASE_URL: postgres://postgres:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/roomote
REDIS_URL: redis://redis:6379
R_APP_URL: ${SERVICE_URL_WEB}
R_PUBLIC_URL: ${SERVICE_URL_WEB}
- TRPC_URL: ${SERVICE_URL_API}
+ R_TRPC_URL: ${SERVICE_URL_API}
S3_ENDPOINT: http://minio:9000
S3_PRESIGN_ENDPOINT: ${SERVICE_URL_MINIO}
@@ -224,7 +224,7 @@ services:
# controller reads the release version from the VERSION file inside
# the archive. Do not unset: the fallback is GitHub worker releases,
# which do not exist for develop builds.
- DOCKER_WORKER_RELEASE_PATH: /roomote/releases/worker-current.tar.gz
+ R_DOCKER_WORKER_RELEASE_PATH: /roomote/releases/worker-current.tar.gz
# Must match the name of this stack's Docker network (declared at the
# bottom of this file) so spawned worker containers can reach the
# stack. Verify with `docker network ls` on the Coolify server.
@@ -233,9 +233,9 @@ services:
# optional preview-proxy services. Spawned workers receive per-task
# networks, so they do not join roomote_default or gain access to its
# datastores.
- DOCKER_WORKER_NETWORK: roomote_default
+ R_DOCKER_WORKER_NETWORK: roomote_default
# Keep false unless the host enforces an equivalent storage quota.
- DOCKER_WORKER_ALLOW_UNBOUNDED_DISK: ${DOCKER_WORKER_ALLOW_UNBOUNDED_DISK:-false}
+ R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK: ${R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK:-false}
healthcheck:
test:
[
@@ -284,9 +284,9 @@ services:
# SERVICE_FQDN_PREVIEWPROXY_8081: ''
# <<: *roomote-shared-env
# PORT: 8081
- # PREVIEW_PROXY_BASE_URL: https://previews.
+ # R_PREVIEW_PROXY_BASE_URL: https://previews.
# NEXT_PUBLIC_PREVIEW_PROXY_BASE_URL: https://previews.
- # PREVIEW_DOMAINS: previews.
+ # R_PREVIEW_DOMAINS: previews.
# healthcheck:
# test:
# [
@@ -304,7 +304,7 @@ volumes:
minio_data:
networks:
- # Named explicitly so the controller's DOCKER_WORKER_NETWORK value is
+ # Named explicitly so the controller's R_DOCKER_WORKER_NETWORK value is
# stable across redeploys. Coolify may attach additional networks of its
# own; that is fine as long as this one exists.
default:
diff --git a/deploy/deployment-catalog.json b/deploy/deployment-catalog.json
index fdad3208..8fc3cc72 100644
--- a/deploy/deployment-catalog.json
+++ b/deploy/deployment-catalog.json
@@ -74,6 +74,6 @@
"REDIS_URL",
"S3_ENDPOINT",
"S3_PRESIGN_ENDPOINT",
- "DEFAULT_COMPUTE_PROVIDER"
+ "R_DEFAULT_COMPUTE_PROVIDER"
]
}
diff --git a/deploy/fly/README.md b/deploy/fly/README.md
index 3b5c13e4..0e28140a 100644
--- a/deploy/fly/README.md
+++ b/deploy/fly/README.md
@@ -23,7 +23,7 @@ other PaaS-shaped paths, see [deploy/railway](../railway/README.md) and
`/_roomote-api` path routing. A Fly app has one public hostname and Fly
Proxy routes by port, not hostname or process group, so the web app owns
port 443 and the API is exposed on port **8443** of the same hostname:
- `TRPC_URL` is `https://.fly.dev:8443`. Fly's `*.fly.dev` certificate
+ `R_TRPC_URL` is `https://.fly.dev:8443`. Fly's `*.fly.dev` certificate
covers TLS-handled ports other than 443, and GitHub webhooks and
hosted-sandbox workers call that origin directly (both support explicit
ports in URLs). Slack webhooks arrive at the web origin and are proxied to
@@ -35,7 +35,7 @@ other PaaS-shaped paths, see [deploy/railway](../railway/README.md) and
- **No Docker socket.** The `docker` sandbox provider cannot run on Fly
Machines. Task execution must use a hosted sandbox provider: Modal
(default), E2B, or Daytona. Those only need outbound HTTPS plus API
- credentials. The config sets `EXCLUDED_COMPUTE_PROVIDERS=docker` so the
+ credentials. The config sets `R_EXCLUDED_COMPUTE_PROVIDERS=docker` so the
unusable provider never appears in setup or sandbox selection.
- **Managed data services replace the bundled datastores.** Fly Managed
Postgres provides `DATABASE_URL`, Upstash Redis (via `fly redis create`)
@@ -166,7 +166,7 @@ Notes:
users browse. Do not set `R_PUBLIC_URL` — it is optional and the app
falls back to `R_APP_URL` everywhere it would apply. See
[Attaching a custom domain](#attaching-a-custom-domain).
-- Leave `DOCKER_WORKER_IMAGE` and `MODAL_BASE_IMAGE_REF` **unset**. The app
+- Leave `R_DOCKER_WORKER_IMAGE` and `MODAL_BASE_IMAGE_REF` **unset**. The app
derives both from the `RELEASE_VERSION` baked into the running image, so
they always match the deployed build. Setting them explicitly overrides
the derivation and silently pins the worker to whatever version the value
@@ -179,7 +179,7 @@ Notes:
- Leave `JOB_AUTH_*` and `PREVIEW_AUTH_*` unset —
`R_AUTO_GENERATE_KEYS=true` manages them. If you later provide
explicit values, they take precedence over the persisted keypairs.
-- Leave `PREVIEW_PROXY_BASE_URL` and `PREVIEW_DOMAINS` unset unless you
+- Leave `R_PREVIEW_PROXY_BASE_URL` and `R_PREVIEW_DOMAINS` unset unless you
enable live previews. Roomote boots without them; previews report as not
configured in **Settings → Live Previews** until set.
- Changing secrets on a deployed app restarts its Machines; `fly secrets
@@ -211,7 +211,7 @@ workers. Pre-create the bucket, or set `S3_AUTO_CREATE_BUCKET=true` in
3. Create the founding admin account (email/password works immediately;
Slack or Microsoft sign-in can be added later).
4. Connect GitHub with **Create GitHub App** — the manifest flow derives the
- callback and webhook URLs from `R_APP_URL` and `TRPC_URL`, so no
+ callback and webhook URLs from `R_APP_URL` and `R_TRPC_URL`, so no
manual URL entry is needed.
5. Enter the sandbox provider credentials (Modal token pair for the default)
and the model provider key when the wizard asks. When swapping to E2B or
@@ -241,7 +241,7 @@ GitHub App created by the wizard) keep the callback URLs they were created
with, so reconnect or update those in their provider settings if you change
the domain after onboarding.
-`TRPC_URL` can stay on `https://.fly.dev:8443` — hosted-sandbox workers
+`R_TRPC_URL` can stay on `https://.fly.dev:8443` — hosted-sandbox workers
and webhooks call it directly, and it never needs to match the domain users
browse. If you move it onto the custom domain, keep the `:8443` port; the
certificate from `fly certs add` covers TLS-handled ports other than 443.
@@ -298,8 +298,8 @@ preview proxy cannot share it. Run the proxy as a second Fly app instead:
previews.` and `fly certs add -a -previews
'*.previews.'`; the wildcard needs the DNS challenge from
Fly's certificate docs).
-3. On the **main** app, set `PREVIEW_PROXY_BASE_URL`,
- `NEXT_PUBLIC_PREVIEW_PROXY_BASE_URL`, and `PREVIEW_DOMAINS` to
+3. On the **main** app, set `R_PREVIEW_PROXY_BASE_URL`,
+ `NEXT_PUBLIC_PREVIEW_PROXY_BASE_URL`, and `R_PREVIEW_DOMAINS` to
`https://previews.` (the `NEXT_PUBLIC_` variant is what the
web client uses to build preview links), add the same three values to the
previews app, and redeploy both.
diff --git a/deploy/fly/fly.toml b/deploy/fly/fly.toml
index 7dec7f02..25f799b9 100644
--- a/deploy/fly/fly.toml
+++ b/deploy/fly/fly.toml
@@ -16,7 +16,7 @@
# - Fly Proxy is the HTTPS edge. There is no bundled Caddy and no
# `/_roomote-api` path routing. A Fly app has one public hostname, and Fly
# Proxy routes by port, not process group, so the web app owns 443 and the
-# API is exposed on port 8443 of the same hostname: `TRPC_URL` is
+# API is exposed on port 8443 of the same hostname: `R_TRPC_URL` is
# `https://.fly.dev:8443`. The `*.fly.dev` certificate covers
# TLS-handled ports other than 443, and GitHub webhooks and hosted-sandbox
# workers call that origin directly.
@@ -26,7 +26,7 @@
# as the Railway template).
# - Fly Machines have no Docker socket, so task execution must use a hosted
# sandbox provider (modal by default; e2b and daytona also work).
-# EXCLUDED_COMPUTE_PROVIDERS=docker hides the unusable provider.
+# R_EXCLUDED_COMPUTE_PROVIDERS=docker hides the unusable provider.
# - Managed resources replace the bundled datastores: Fly Managed Postgres
# (`fly mpg create` + `fly mpg attach` sets DATABASE_URL), Upstash Redis
# (`fly redis create`, set REDIS_URL), and Tigris object storage
@@ -42,7 +42,7 @@
# - The config tracks the mutable `:main` image alias (stable main branch
# builds) and contains no version strings. `fly deploy` resolves the
# alias at deploy time, so redeploying is upgrading. The images bake
-# RELEASE_VERSION; the app derives DOCKER_WORKER_IMAGE and
+# RELEASE_VERSION; the app derives R_DOCKER_WORKER_IMAGE and
# MODAL_BASE_IMAGE_REF from it, and the controller reads the worker
# release version from the VERSION file inside worker-current.tar.gz.
# Production deployments can pin an immutable `v*` / `main-` tag in
@@ -81,15 +81,15 @@ swap_size_mb = 512
PORT = '8080'
ROOMOTE_DOCKER_LOAD_ENV_FILE = 'false'
R_AUTO_GENERATE_KEYS = 'true'
- DEFAULT_COMPUTE_PROVIDER = 'modal'
- EXCLUDED_COMPUTE_PROVIDERS = 'docker'
+ R_DEFAULT_COMPUTE_PROVIDER = 'modal'
+ R_EXCLUDED_COMPUTE_PROVIDERS = 'docker'
# The single canonical-origin knob: the URL users browse. To attach a
# custom domain, add the cert to this app and change only this value
# (R_PUBLIC_URL stays unset; the app falls back to R_APP_URL).
R_APP_URL = 'https://roomote-CHANGEME.fly.dev'
# Hosted-sandbox workers and GitHub webhooks call this origin directly;
# it is the api service on port 8443 of the same Fly hostname.
- TRPC_URL = 'https://roomote-CHANGEME.fly.dev:8443'
+ R_TRPC_URL = 'https://roomote-CHANGEME.fly.dev:8443'
# Tigris object storage (S3-compatible). `fly storage create` provisions
# the bucket and prints the credentials to copy into the S3_* secrets.
S3_ENDPOINT = 'https://fly.storage.tigris.dev'
@@ -101,7 +101,7 @@ swap_size_mb = 512
# release version from the VERSION file inside the archive. Do not unset:
# the fallback is GitHub worker releases, which do not exist for develop
# builds. Only the controller consumes it; it is harmless elsewhere.
- DOCKER_WORKER_RELEASE_PATH = '/roomote/releases/worker-current.tar.gz'
+ R_DOCKER_WORKER_RELEASE_PATH = '/roomote/releases/worker-current.tar.gz'
[processes]
web = 'web'
diff --git a/deploy/host/roomote b/deploy/host/roomote
index 928ee74f..c2d36b5d 100755
--- a/deploy/host/roomote
+++ b/deploy/host/roomote
@@ -529,7 +529,7 @@ cmd_upgrade() {
image_namespace="$(read_env_value IMAGE_NAMESPACE)"
image_registry="${image_registry:-ghcr.io}"
image_namespace="${image_namespace:-roocodeinc}"
- previous_worker_image="$(read_env_value DOCKER_WORKER_IMAGE)"
+ previous_worker_image="$(read_env_value R_DOCKER_WORKER_IMAGE)"
worker_image="$image_registry/$image_namespace/roomote-worker:$version"
app_domain="$(read_env_value ROOMOTE_APP_DOMAIN)"
[ -n "$app_domain" ] || die "ROOMOTE_APP_DOMAIN is not set in $env_file"
@@ -550,18 +550,18 @@ cmd_upgrade() {
if [ -n "$previous_version" ] && [ "$previous_version" != "$version" ]; then
set_env_value ROOMOTE_PREVIOUS_VERSION "$previous_version"
fi
- set_env_value TRPC_URL "https://$app_domain/_roomote-api"
+ set_env_value R_TRPC_URL "https://$app_domain/_roomote-api"
set_env_value IMAGE_REGISTRY "$image_registry"
set_env_value IMAGE_NAMESPACE "$image_namespace"
- set_env_value DOCKER_WORKER_IMAGE "$worker_image"
+ set_env_value R_DOCKER_WORKER_IMAGE "$worker_image"
# worker-current.tar.gz always matches the running image (see install.sh);
# also repairs older installs that stored a version-derived path.
- set_env_value DOCKER_WORKER_RELEASE_PATH '/roomote/releases/worker-current.tar.gz'
+ set_env_value R_DOCKER_WORKER_RELEASE_PATH '/roomote/releases/worker-current.tar.gz'
# Keep the installer/deployer-managed Modal base image ref in sync with the
# new worker image. The wizard stores the selected sandbox provider in the
# database, not in the env file, so this must not gate on
- # DEFAULT_COMPUTE_PROVIDER. A different non-empty value is an operator
+ # R_DEFAULT_COMPUTE_PROVIDER. A different non-empty value is an operator
# override and is left untouched.
modal_base_image_ref="$(read_env_value MODAL_BASE_IMAGE_REF)"
if [ -z "$modal_base_image_ref" ] || [ "$modal_base_image_ref" = "$previous_worker_image" ]; then
@@ -615,7 +615,7 @@ cmd_rollback() {
stop_task_workers() {
local worker_ids worker_network
- worker_network="$(read_env_value DOCKER_WORKER_NETWORK)"
+ worker_network="$(read_env_value R_DOCKER_WORKER_NETWORK)"
worker_network="${worker_network:-roomote_worker}"
worker_ids="$(docker ps --filter "network=$worker_network" --format '{{.ID}} {{.Names}}' | awk '$2 ~ /^roomote-worker-/ { print $1 }')"
if [ -n "$worker_ids" ]; then
@@ -631,7 +631,7 @@ collect_image_identities() {
references_file="$(mktemp)"
compose config --images >"$references_file"
- worker_image="$(read_env_value DOCKER_WORKER_IMAGE)"
+ worker_image="$(read_env_value R_DOCKER_WORKER_IMAGE)"
[ -z "$worker_image" ] || printf '%s\n' "$worker_image" >>"$references_file"
: >"$output_file"
diff --git a/deploy/host/tests/backup-restore.integration.sh b/deploy/host/tests/backup-restore.integration.sh
index e3e8cae3..ede8ff81 100755
--- a/deploy/host/tests/backup-restore.integration.sh
+++ b/deploy/host/tests/backup-restore.integration.sh
@@ -42,8 +42,8 @@ S3_BUCKET_ARTIFACTS=roomote-artifacts
ENCRYPTION_KEY=$encryption_key
ROOMOTE_VERSION=backup-ci
ROOMOTE_COMPOSE_NETWORK=roomote_backup_ci_default
-DOCKER_WORKER_IMAGE=redis:7-alpine
-DOCKER_WORKER_NETWORK=roomote_backup_ci_worker
+R_DOCKER_WORKER_IMAGE=redis:7-alpine
+R_DOCKER_WORKER_NETWORK=roomote_backup_ci_worker
EOF
cat >"$root/docker-compose.prod.yml" <<'EOF'
name: roomote-backup-ci
diff --git a/deploy/install.sh b/deploy/install.sh
index 0d9abc03..2a35e263 100755
--- a/deploy/install.sh
+++ b/deploy/install.sh
@@ -486,7 +486,7 @@ else
set_env_value ENCRYPTION_KEY "$(openssl rand -base64 32 | tr -d '\n')"
set_env_value ARTIFACT_SIGNING_KEY "$(openssl rand -base64 32 | tr -d '\n')"
set_env_value DASHBOARD_PASSWORD "$(openssl rand -base64 24 | tr -d '\n')"
- set_env_value DEFAULT_COMPUTE_PROVIDER docker
+ set_env_value R_DEFAULT_COMPUTE_PROVIDER docker
set_env_value COMPOSE_PROFILES local-postgres
fi
@@ -503,7 +503,7 @@ worker_image="$image_registry/$image_namespace/roomote-worker:$roomote_version"
# the previously configured worker image, so operators who pick Modal in the
# setup wizard only need their Modal token pair. A different non-empty value
# is an operator override and is left untouched.
-previous_worker_image="$(read_env_value DOCKER_WORKER_IMAGE)"
+previous_worker_image="$(read_env_value R_DOCKER_WORKER_IMAGE)"
modal_base_image_ref="$(read_env_value MODAL_BASE_IMAGE_REF)"
if [ -z "$modal_base_image_ref" ] || [ "$modal_base_image_ref" = "$previous_worker_image" ]; then
set_env_value MODAL_BASE_IMAGE_REF "$worker_image"
@@ -513,17 +513,17 @@ set_env_value ROOMOTE_VERSION "$roomote_version"
set_env_value ROOMOTE_REPO "$repo"
set_env_value ROOMOTE_APP_DOMAIN "$domain"
set_env_value ROOMOTE_PREVIEW_DOMAIN "$preview_domain"
-set_env_value TRPC_URL "https://$domain/_roomote-api"
+set_env_value R_TRPC_URL "https://$domain/_roomote-api"
set_env_value IMAGE_REGISTRY "$image_registry"
set_env_value IMAGE_NAMESPACE "$image_namespace"
-set_env_value DOCKER_WORKER_IMAGE "$worker_image"
-set_env_value DOCKER_WORKER_PLATFORM "$worker_platform"
-set_env_value DOCKER_WORKER_NETWORK 'roomote_worker'
+set_env_value R_DOCKER_WORKER_IMAGE "$worker_image"
+set_env_value R_DOCKER_WORKER_PLATFORM "$worker_platform"
+set_env_value R_DOCKER_WORKER_NETWORK 'roomote_worker'
# worker-current.tar.gz is baked into every app image alongside the
# worker-v archive and always matches the running image, including
# for mutable channel tags like develop where the env version and the image's
# baked RELEASE_VERSION differ.
-set_env_value DOCKER_WORKER_RELEASE_PATH '/roomote/releases/worker-current.tar.gz'
+set_env_value R_DOCKER_WORKER_RELEASE_PATH '/roomote/releases/worker-current.tar.gz'
chown root:root "$env_file"
chmod 600 "$env_file"
diff --git a/deploy/railway/README.md b/deploy/railway/README.md
index fc6ac680..cad968a3 100644
--- a/deploy/railway/README.md
+++ b/deploy/railway/README.md
@@ -22,14 +22,14 @@ Compose paths in [SELF_HOSTING.md](../../SELF_HOSTING.md) instead.
- **No Caddy edge.** Railway terminates HTTPS and gives every service its own
public domain. The web app and the API run on separate origins, so
- `TRPC_URL` points at the api service's public domain with no
+ `R_TRPC_URL` points at the api service's public domain with no
`/_roomote-api` path prefix. GitHub webhooks and hosted-sandbox workers
call that API origin directly, and Slack webhooks arrive at the web origin
and are proxied to the API internally.
- **No Docker socket.** The `docker` sandbox provider cannot run on Railway.
Task execution must use a hosted sandbox provider: Modal (template
default), E2B, or Daytona. Those only need outbound HTTPS plus API
- credentials. The template sets `EXCLUDED_COMPUTE_PROVIDERS=docker` so the
+ credentials. The template sets `R_EXCLUDED_COMPUTE_PROVIDERS=docker` so the
unusable provider never appears in setup or sandbox selection.
- **No openssl provisioning step.** The template sets
`R_AUTO_GENERATE_KEYS=true`, so Roomote generates the `JOB_AUTH_*` /
@@ -88,10 +88,10 @@ moves on every matching build:
Nothing else in either template encodes a version:
- The images bake `RELEASE_VERSION` at build time, and the app derives
- `DOCKER_WORKER_IMAGE` and `MODAL_BASE_IMAGE_REF` from it when those are
+ `R_DOCKER_WORKER_IMAGE` and `MODAL_BASE_IMAGE_REF` from it when those are
unset. Set them only to override the derived worker image.
- The controller reads the worker release version from the `VERSION` file
- inside `worker-current.tar.gz`, so `DOCKER_WORKER_RELEASE_PATH` is a
+ inside `worker-current.tar.gz`, so `R_DOCKER_WORKER_RELEASE_PATH` is a
constant.
To pin instead (recommended for production deployments): put the same
@@ -155,15 +155,15 @@ S3_BUCKET_ARTIFACTS=roomote-artifacts
S3_AUTO_CREATE_BUCKET=true
SETUP_TOKEN=${{secret(32)}}
R_PING_BASE_URL=https://ping.roomote.dev
-PREVIEW_PROXY_BASE_URL=
+R_PREVIEW_PROXY_BASE_URL=
NEXT_PUBLIC_PREVIEW_PROXY_BASE_URL=
-PREVIEW_DOMAINS=
-DEFAULT_COMPUTE_PROVIDER=modal
-EXCLUDED_COMPUTE_PROVIDERS=docker
+R_PREVIEW_DOMAINS=
+R_DEFAULT_COMPUTE_PROVIDER=modal
+R_EXCLUDED_COMPUTE_PROVIDERS=docker
DATABASE_URL=${{Postgres.DATABASE_URL}}
REDIS_URL=${{Redis.REDIS_URL}}
R_APP_URL=https://${{web.RAILWAY_PUBLIC_DOMAIN}}
-TRPC_URL=https://${{api.RAILWAY_PUBLIC_DOMAIN}}
+R_TRPC_URL=https://${{api.RAILWAY_PUBLIC_DOMAIN}}
S3_ENDPOINT=http://${{minio.RAILWAY_PRIVATE_DOMAIN}}:9000
S3_PRESIGN_ENDPOINT=https://${{minio.RAILWAY_PUBLIC_DOMAIN}}
```
@@ -183,15 +183,15 @@ S3_REGION=${{api.S3_REGION}}
S3_BUCKET_ARTIFACTS=${{api.S3_BUCKET_ARTIFACTS}}
SETUP_TOKEN=${{api.SETUP_TOKEN}}
R_PING_BASE_URL=${{api.R_PING_BASE_URL}}
-PREVIEW_PROXY_BASE_URL=${{api.PREVIEW_PROXY_BASE_URL}}
+R_PREVIEW_PROXY_BASE_URL=${{api.R_PREVIEW_PROXY_BASE_URL}}
NEXT_PUBLIC_PREVIEW_PROXY_BASE_URL=${{api.NEXT_PUBLIC_PREVIEW_PROXY_BASE_URL}}
-PREVIEW_DOMAINS=${{api.PREVIEW_DOMAINS}}
-DEFAULT_COMPUTE_PROVIDER=${{api.DEFAULT_COMPUTE_PROVIDER}}
-EXCLUDED_COMPUTE_PROVIDERS=${{api.EXCLUDED_COMPUTE_PROVIDERS}}
+R_PREVIEW_DOMAINS=${{api.R_PREVIEW_DOMAINS}}
+R_DEFAULT_COMPUTE_PROVIDER=${{api.R_DEFAULT_COMPUTE_PROVIDER}}
+R_EXCLUDED_COMPUTE_PROVIDERS=${{api.R_EXCLUDED_COMPUTE_PROVIDERS}}
DATABASE_URL=${{Postgres.DATABASE_URL}}
REDIS_URL=${{Redis.REDIS_URL}}
R_APP_URL=${{api.R_APP_URL}}
-TRPC_URL=${{api.TRPC_URL}}
+R_TRPC_URL=${{api.R_TRPC_URL}}
S3_ENDPOINT=${{api.S3_ENDPOINT}}
S3_PRESIGN_ENDPOINT=${{api.S3_PRESIGN_ENDPOINT}}
```
@@ -199,14 +199,14 @@ S3_PRESIGN_ENDPOINT=${{api.S3_PRESIGN_ENDPOINT}}
controller: the same block **plus**
```sh
-DOCKER_WORKER_RELEASE_PATH=/roomote/releases/worker-current.tar.gz
+R_DOCKER_WORKER_RELEASE_PATH=/roomote/releases/worker-current.tar.gz
```
The worker release archive is baked into the app image, and the controller
uploads it into hosted sandboxes at spawn time — no shared volume is
needed. The version-less `worker-current.tar.gz` name works because the
controller reads the release version from the `VERSION` file inside the
-archive. Do not leave `DOCKER_WORKER_RELEASE_PATH` unset: without it the
+archive. Do not leave `R_DOCKER_WORKER_RELEASE_PATH` unset: without it the
controller falls back to fetching GitHub worker releases, which only exist
for tagged `v*` releases — not for `develop` or `main` branch builds.
@@ -227,7 +227,7 @@ Notes:
entered before first boot. Do not set `R_PUBLIC_URL` — it is
optional and the app falls back to `R_APP_URL` everywhere it would
apply. See [Attaching a custom domain](#attaching-a-custom-domain).
-- Leave `DOCKER_WORKER_IMAGE` and `MODAL_BASE_IMAGE_REF` **unset**. The app
+- Leave `R_DOCKER_WORKER_IMAGE` and `MODAL_BASE_IMAGE_REF` **unset**. The app
derives both from the `RELEASE_VERSION` baked into the running image, so
they always match the deployed build. Setting them explicitly overrides
the derivation and silently pins the worker to whatever version the value
@@ -302,7 +302,7 @@ logs a warning when creation fails).
3. Create the founding admin account (email/password works immediately;
Slack or Microsoft sign-in can be added later).
4. Connect GitHub with **Create GitHub App** — the manifest flow derives the
- callback and webhook URLs from `R_APP_URL` and `TRPC_URL`, so no
+ callback and webhook URLs from `R_APP_URL` and `R_TRPC_URL`, so no
manual URL entry is needed.
5. Enter the sandbox provider credentials (Modal token pair for the default)
and the model provider key when the wizard asks. When swapping to E2B or
@@ -356,10 +356,10 @@ GitHub App created by the wizard) keep the callback URLs they were created
with, so reconnect or update those in their provider settings if you change
the domain after onboarding.
-Leave `TRPC_URL` on the api service's own domain — hosted-sandbox workers
+Leave `R_TRPC_URL` on the api service's own domain — hosted-sandbox workers
and webhooks call it directly, and it never needs to match the domain users
browse. (A custom domain on the api or MinIO services works the same way if
-you want one: they are separate values on api, `TRPC_URL` and
+you want one: they are separate values on api, `R_TRPC_URL` and
`S3_PRESIGN_ENDPOINT`.)
## Enabling live previews (optional)
@@ -372,9 +372,9 @@ domain, which requires a domain you control:
as custom domains on the preview-proxy service, following Railway's
wildcard-domain docs.
2. Fill in the three empty `PREVIEW_*` values on **api**:
- `PREVIEW_PROXY_BASE_URL=https://previews.`,
+ `R_PREVIEW_PROXY_BASE_URL=https://previews.`,
`NEXT_PUBLIC_PREVIEW_PROXY_BASE_URL=https://previews.`, and
- `PREVIEW_DOMAINS=previews.`, then redeploy the app
+ `R_PREVIEW_DOMAINS=previews.`, then redeploy the app
services. The other services already reference `${{api.*}}` for all
three; the `NEXT_PUBLIC_` variant is what the web client uses to build
preview links, so it must reach the `web` service.
diff --git a/deploy/railway/template.yaml b/deploy/railway/template.yaml
index 77d78180..cb272b15 100644
--- a/deploy/railway/template.yaml
+++ b/deploy/railway/template.yaml
@@ -25,7 +25,7 @@
# - Each published template tracks one mutable channel alias — `:develop`
# (every develop build) or `:main` (every main build) — and contains no
# version strings. The images bake RELEASE_VERSION; the app derives
-# DOCKER_WORKER_IMAGE and MODAL_BASE_IMAGE_REF from it, and the controller
+# R_DOCKER_WORKER_IMAGE and MODAL_BASE_IMAGE_REF from it, and the controller
# reads the worker release version from the VERSION file inside
# worker-current.tar.gz. Production deployments can pin an immutable tag
# (v*, develop-, or main-) in the five image fields instead;
@@ -34,7 +34,7 @@
# (README.md "Auto-deploying every develop build").
# - Railway has no Docker socket, so task execution must use a hosted
# sandbox provider (modal by default; e2b and daytona also work).
-# EXCLUDED_COMPUTE_PROVIDERS=docker hides the unusable provider.
+# R_EXCLUDED_COMPUTE_PROVIDERS=docker hides the unusable provider.
# - Zero required deploy-time inputs. Modal tokens and model provider keys
# are entered in the /setup wizard after first boot and stored encrypted
# in Postgres. Auth keypairs come from R_AUTO_GENERATE_KEYS=true.
@@ -44,7 +44,7 @@
# first deploy instead of editing the variable after hitting 403
# "Invalid origin" errors.
# - The api service must be publicly reachable: hosted sandboxes workers and
-# GitHub webhooks call `TRPC_URL` directly.
+# GitHub webhooks call `R_TRPC_URL` directly.
# - Both published images are public on GHCR: Railway pulls roomote-app and
# hosted-sandbox builders pull roomote-worker anonymously, so no registry
# credentials are needed.
@@ -142,13 +142,13 @@ services:
# To enable: set to https://previews. / https://previews.
# / previews. after attaching the wildcard custom domain to
# the preview-proxy service — see "Enabling live previews" in README.md.
- PREVIEW_PROXY_BASE_URL: ''
+ R_PREVIEW_PROXY_BASE_URL: ''
NEXT_PUBLIC_PREVIEW_PROXY_BASE_URL: ''
- PREVIEW_DOMAINS: ''
+ R_PREVIEW_DOMAINS: ''
SETUP_TOKEN: ${{secret(32)}} # gates /setup; required outside local dev (see design notes)
R_PING_BASE_URL: https://ping.roomote.dev # anonymous-analytics / version-check pings
- DEFAULT_COMPUTE_PROVIDER: modal
- EXCLUDED_COMPUTE_PROVIDERS: docker
+ R_DEFAULT_COMPUTE_PROVIDER: modal
+ R_EXCLUDED_COMPUTE_PROVIDERS: docker
DATABASE_URL: ${{Postgres.DATABASE_URL}}
REDIS_URL: ${{Redis.REDIS_URL}}
# The single canonical-origin knob, and the template's one (optional)
@@ -166,7 +166,7 @@ services:
# domain to the web service, finish DNS setup, and open /setup on the
# custom domain."
R_APP_URL: https://${{web.RAILWAY_PUBLIC_DOMAIN}}
- TRPC_URL: https://${{api.RAILWAY_PUBLIC_DOMAIN}}
+ R_TRPC_URL: https://${{api.RAILWAY_PUBLIC_DOMAIN}}
S3_ENDPOINT: http://${{minio.RAILWAY_PRIVATE_DOMAIN}}:9000
S3_PRESIGN_ENDPOINT: https://${{minio.RAILWAY_PUBLIC_DOMAIN}}
@@ -194,15 +194,15 @@ services:
# Empty until previews are enabled; the web client builds preview
# links from NEXT_PUBLIC_PREVIEW_PROXY_BASE_URL, so the references
# live in the shared block rather than only on preview-proxy.
- PREVIEW_PROXY_BASE_URL: ${{api.PREVIEW_PROXY_BASE_URL}}
+ R_PREVIEW_PROXY_BASE_URL: ${{api.R_PREVIEW_PROXY_BASE_URL}}
NEXT_PUBLIC_PREVIEW_PROXY_BASE_URL: ${{api.NEXT_PUBLIC_PREVIEW_PROXY_BASE_URL}}
- PREVIEW_DOMAINS: ${{api.PREVIEW_DOMAINS}}
- DEFAULT_COMPUTE_PROVIDER: ${{api.DEFAULT_COMPUTE_PROVIDER}}
- EXCLUDED_COMPUTE_PROVIDERS: ${{api.EXCLUDED_COMPUTE_PROVIDERS}}
+ R_PREVIEW_DOMAINS: ${{api.R_PREVIEW_DOMAINS}}
+ R_DEFAULT_COMPUTE_PROVIDER: ${{api.R_DEFAULT_COMPUTE_PROVIDER}}
+ R_EXCLUDED_COMPUTE_PROVIDERS: ${{api.R_EXCLUDED_COMPUTE_PROVIDERS}}
DATABASE_URL: ${{Postgres.DATABASE_URL}}
REDIS_URL: ${{Redis.REDIS_URL}}
R_APP_URL: ${{api.R_APP_URL}}
- TRPC_URL: ${{api.TRPC_URL}}
+ R_TRPC_URL: ${{api.R_TRPC_URL}}
S3_ENDPOINT: ${{api.S3_ENDPOINT}}
S3_PRESIGN_ENDPOINT: ${{api.S3_PRESIGN_ENDPOINT}}
@@ -218,7 +218,7 @@ services:
# version from the VERSION file inside the archive. Do not unset: the
# fallback is GitHub worker releases, which only exist for tagged v*
# releases, not develop or main branch builds.
- DOCKER_WORKER_RELEASE_PATH: /roomote/releases/worker-current.tar.gz
+ R_DOCKER_WORKER_RELEASE_PATH: /roomote/releases/worker-current.tar.gz
bullmq:
image: app
diff --git a/deploy/render/README.md b/deploy/render/README.md
index ac88f43c..7f216b0b 100644
--- a/deploy/render/README.md
+++ b/deploy/render/README.md
@@ -17,14 +17,14 @@ other PaaS paths, see [deploy/railway](../railway/README.md) and
- **No Caddy edge.** Render terminates HTTPS and gives every web service its
own public `onrender.com` domain. The web app and the API run on separate
- origins, so `TRPC_URL` points at the api service's public domain with no
+ origins, so `R_TRPC_URL` points at the api service's public domain with no
`/_roomote-api` path prefix. GitHub webhooks and hosted-sandbox workers
call that API origin directly, and Slack webhooks arrive at the web origin
and are proxied to the API internally.
- **No Docker socket.** The `docker` sandbox provider cannot run on Render.
Task execution must use a hosted sandbox provider: Modal (Blueprint
default), E2B, or Daytona. Those only need outbound HTTPS plus API
- credentials. The Blueprint sets `EXCLUDED_COMPUTE_PROVIDERS=docker` so the
+ credentials. The Blueprint sets `R_EXCLUDED_COMPUTE_PROVIDERS=docker` so the
unusable provider never appears in setup or sandbox selection.
- **No openssl provisioning step.** The Blueprint sets
`R_AUTO_GENERATE_KEYS=true`, so Roomote generates the `JOB_AUTH_*` /
@@ -43,7 +43,7 @@ other PaaS paths, see [deploy/railway](../railway/README.md) and
`https://` string, so each app service receives host-only
references (`ROOMOTE_WEB_HOST`, `ROOMOTE_API_HOST`, `ROOMOTE_MINIO_HOST`,
`ROOMOTE_MINIO_HOSTPORT`) and the image's entrypoint dispatcher derives
- the URL-shaped variables (`R_APP_URL`, `TRPC_URL`, `S3_ENDPOINT`,
+ the URL-shaped variables (`R_APP_URL`, `R_TRPC_URL`, `S3_ENDPOINT`,
`S3_PRESIGN_ENDPOINT`) from them when they are unset.
- **The setup wizard is token-gated by default.** `onrender.com` domains are
publicly reachable, so the Blueprint generates `SETUP_TOKEN`. Copy it from
@@ -121,10 +121,10 @@ the Railway deploy button. Nothing else in the Blueprint encodes a
version:
- The images bake `RELEASE_VERSION` at build time, and the app derives
- `DOCKER_WORKER_IMAGE` and `MODAL_BASE_IMAGE_REF` from it when those are
+ `R_DOCKER_WORKER_IMAGE` and `MODAL_BASE_IMAGE_REF` from it when those are
unset. Set them only to override the derived worker image.
- The controller reads the worker release version from the `VERSION` file
- inside `worker-current.tar.gz`, so `DOCKER_WORKER_RELEASE_PATH` is a
+ inside `worker-current.tar.gz`, so `R_DOCKER_WORKER_RELEASE_PATH` is a
constant.
Render does not redeploy when a mutable alias moves. To pick up a new
@@ -190,9 +190,9 @@ S3_ACCESS_KEY_ID=roomote
S3_REGION=us-east-1
S3_BUCKET_ARTIFACTS=roomote-artifacts
S3_AUTO_CREATE_BUCKET=true
-DEFAULT_COMPUTE_PROVIDER=modal
-EXCLUDED_COMPUTE_PROVIDERS=docker
-DOCKER_WORKER_RELEASE_PATH=/roomote/releases/worker-current.tar.gz
+R_DEFAULT_COMPUTE_PROVIDER=modal
+R_EXCLUDED_COMPUTE_PROVIDERS=docker
+R_DOCKER_WORKER_RELEASE_PATH=/roomote/releases/worker-current.tar.gz
```
Each app service additionally references:
@@ -212,13 +212,13 @@ those host references at startup (explicitly set values always win):
```sh
R_APP_URL=https://$ROOMOTE_WEB_HOST
-TRPC_URL=https://$ROOMOTE_API_HOST
+R_TRPC_URL=https://$ROOMOTE_API_HOST
S3_ENDPOINT=http://$ROOMOTE_MINIO_HOSTPORT
S3_PRESIGN_ENDPOINT=https://$ROOMOTE_MINIO_HOST
```
The `roomote-shared` group also sets
-`DOCKER_WORKER_RELEASE_PATH=/roomote/releases/worker-current.tar.gz`, which
+`R_DOCKER_WORKER_RELEASE_PATH=/roomote/releases/worker-current.tar.gz`, which
only the controller reads. The worker release archive is baked into the app
image, and the controller uploads it into hosted sandboxes at spawn
time — no shared volume is needed. The version-less `worker-current.tar.gz`
@@ -233,7 +233,7 @@ Notes:
origin users browse. Do not set `R_PUBLIC_URL` — it is optional and
the app falls back to `R_APP_URL` everywhere it would apply. See
[Attaching a custom domain](#attaching-a-custom-domain).
-- Leave `DOCKER_WORKER_IMAGE` and `MODAL_BASE_IMAGE_REF` **unset**. The app
+- Leave `R_DOCKER_WORKER_IMAGE` and `MODAL_BASE_IMAGE_REF` **unset**. The app
derives both from the `RELEASE_VERSION` baked into the running image, so
they always match the deployed build. Setting them explicitly overrides
the derivation and silently pins the worker to whatever version the value
@@ -246,7 +246,7 @@ Notes:
- Leave `JOB_AUTH_*` and `PREVIEW_AUTH_*` unset —
`R_AUTO_GENERATE_KEYS=true` manages them. If you later provide
explicit env values, they take precedence over the persisted keypairs.
-- Leave `PREVIEW_PROXY_BASE_URL` and `PREVIEW_DOMAINS` unset unless you
+- Leave `R_PREVIEW_PROXY_BASE_URL` and `R_PREVIEW_DOMAINS` unset unless you
enable live previews. Roomote boots without them; previews report as not
configured in **Settings → Live Previews** until set.
@@ -279,7 +279,7 @@ allowed to create buckets (the api only logs a warning when creation fails).
3. Create the founding admin account (email/password works immediately;
Slack or Microsoft sign-in can be added later).
4. Connect GitHub with **Create GitHub App** — the manifest flow derives the
- callback and webhook URLs from `R_APP_URL` and `TRPC_URL`, so no
+ callback and webhook URLs from `R_APP_URL` and `R_TRPC_URL`, so no
manual URL entry is needed.
5. Enter the sandbox provider credentials (Modal token pair for the default)
and the model provider key when the wizard asks. When swapping to E2B or
@@ -333,9 +333,9 @@ Live previews need a wildcard domain, which requires a domain you control:
2. Point `previews.` and `*.previews.` at Render
as custom domains on that service, following Render's wildcard custom
domain docs.
-3. Add `PREVIEW_PROXY_BASE_URL=https://previews.`,
+3. Add `R_PREVIEW_PROXY_BASE_URL=https://previews.`,
`NEXT_PUBLIC_PREVIEW_PROXY_BASE_URL=https://previews.`, and
- `PREVIEW_DOMAINS=previews.` to the `roomote-shared`
+ `R_PREVIEW_DOMAINS=previews.` to the `roomote-shared`
environment group. The `NEXT_PUBLIC_` variant is what the web client uses
to build preview links, so the `roomote-web` service must have it.
4. Opt in from **Settings → Live Previews**, which validates the wildcard
diff --git a/deploy/scripts/deploy.sh b/deploy/scripts/deploy.sh
index 0fe9f771..a9603b2f 100755
--- a/deploy/scripts/deploy.sh
+++ b/deploy/scripts/deploy.sh
@@ -250,25 +250,25 @@ printf '\n' >>"$tmp_env"
worker_image="$image_registry/$image_namespace/roomote-worker:$roomote_version"
# worker-current.tar.gz always matches the running image (see install.sh).
worker_release_path="/roomote/releases/worker-current.tar.gz"
-previous_worker_image="$(read_env_value "$tmp_env" DOCKER_WORKER_IMAGE)"
+previous_worker_image="$(read_env_value "$tmp_env" R_DOCKER_WORKER_IMAGE)"
set_env_value "$tmp_env" ROOMOTE_VERSION "$roomote_version"
set_env_value "$tmp_env" ROOMOTE_APP_DOMAIN "$domain"
set_env_value "$tmp_env" ROOMOTE_PREVIEW_DOMAIN "$preview_domain"
-set_env_value "$tmp_env" TRPC_URL "https://$domain/_roomote-api"
+set_env_value "$tmp_env" R_TRPC_URL "https://$domain/_roomote-api"
remove_env_key "$tmp_env" ROOMOTE_API_DOMAIN
set_env_value "$tmp_env" IMAGE_REGISTRY "$image_registry"
set_env_value "$tmp_env" IMAGE_NAMESPACE "$image_namespace"
-set_env_value "$tmp_env" DOCKER_WORKER_IMAGE "$worker_image"
+set_env_value "$tmp_env" R_DOCKER_WORKER_IMAGE "$worker_image"
# The V1 deployer provisions amd64 DigitalOcean droplets only.
-set_env_value "$tmp_env" DOCKER_WORKER_PLATFORM "linux/amd64"
-set_env_value "$tmp_env" DOCKER_WORKER_NETWORK "roomote_worker"
-set_env_value "$tmp_env" DOCKER_WORKER_RELEASE_PATH "$worker_release_path"
+set_env_value "$tmp_env" R_DOCKER_WORKER_PLATFORM "linux/amd64"
+set_env_value "$tmp_env" R_DOCKER_WORKER_NETWORK "roomote_worker"
+set_env_value "$tmp_env" R_DOCKER_WORKER_RELEASE_PATH "$worker_release_path"
set_env_value "$tmp_env" ROOMOTE_DATABASE_MODE "$database_mode"
# Keep the deployer-managed Modal base image ref in sync with the worker
# image. The wizard stores the selected sandbox provider in the database, not
-# in the env file, so this must not gate on DEFAULT_COMPUTE_PROVIDER. A
+# in the env file, so this must not gate on R_DEFAULT_COMPUTE_PROVIDER. A
# different non-empty value is an operator override and is left untouched.
modal_base_image_ref="$(read_env_value "$tmp_env" MODAL_BASE_IMAGE_REF)"
if [ -z "$modal_base_image_ref" ] || [ "$modal_base_image_ref" = "$previous_worker_image" ]; then
diff --git a/deploy/scripts/upgrade.sh b/deploy/scripts/upgrade.sh
index 2276544b..8752bd50 100755
--- a/deploy/scripts/upgrade.sh
+++ b/deploy/scripts/upgrade.sh
@@ -120,14 +120,14 @@ image_registry="${ROOMOTE_IMAGE_REGISTRY_ARG:-$(read_env_value IMAGE_REGISTRY)}"
image_namespace="${ROOMOTE_IMAGE_NAMESPACE_ARG:-$(read_env_value IMAGE_NAMESPACE)}"
image_registry="${image_registry:-ghcr.io}"
image_namespace="${image_namespace:-roocodeinc}"
-previous_worker_image="$(read_env_value DOCKER_WORKER_IMAGE)"
+previous_worker_image="$(read_env_value R_DOCKER_WORKER_IMAGE)"
worker_image="$image_registry/$image_namespace/roomote-worker:$ROOMOTE_VERSION"
# worker-current.tar.gz always matches the running image (see install.sh).
worker_release_path="/roomote/releases/worker-current.tar.gz"
# Keep the installer/deployer-managed Modal base image ref in sync with the
# new worker image. The wizard stores the selected sandbox provider in the
# database, not in the env file, so this must not gate on
-# DEFAULT_COMPUTE_PROVIDER. A different non-empty value is an operator
+# R_DEFAULT_COMPUTE_PROVIDER. A different non-empty value is an operator
# override and is left untouched.
modal_base_image_ref="$(read_env_value MODAL_BASE_IMAGE_REF)"
sync_modal_base_image_ref=false
@@ -176,13 +176,13 @@ awk \
seen_namespace = 1
next
}
- /^(export[[:space:]]+)?DOCKER_WORKER_IMAGE=/ {
- print "DOCKER_WORKER_IMAGE=" worker_image
+ /^(export[[:space:]]+)?R_DOCKER_WORKER_IMAGE=/ {
+ print "R_DOCKER_WORKER_IMAGE=" worker_image
seen_worker = 1
next
}
- /^(export[[:space:]]+)?DOCKER_WORKER_RELEASE_PATH=/ {
- print "DOCKER_WORKER_RELEASE_PATH=" worker_release_path
+ /^(export[[:space:]]+)?R_DOCKER_WORKER_RELEASE_PATH=/ {
+ print "R_DOCKER_WORKER_RELEASE_PATH=" worker_release_path
seen_worker_release_path = 1
next
}
@@ -194,8 +194,8 @@ awk \
/^(export[[:space:]]+)?ROOMOTE_API_DOMAIN=/ {
next
}
- /^(export[[:space:]]+)?TRPC_URL=/ {
- print "TRPC_URL=" trpc_url
+ /^(export[[:space:]]+)?R_TRPC_URL=/ {
+ print "R_TRPC_URL=" trpc_url
seen_trpc = 1
next
}
@@ -211,16 +211,16 @@ awk \
print "IMAGE_NAMESPACE=" image_namespace
}
if (!seen_worker) {
- print "DOCKER_WORKER_IMAGE=" worker_image
+ print "R_DOCKER_WORKER_IMAGE=" worker_image
}
if (!seen_worker_release_path) {
- print "DOCKER_WORKER_RELEASE_PATH=" worker_release_path
+ print "R_DOCKER_WORKER_RELEASE_PATH=" worker_release_path
}
if (sync_modal_base_image_ref == "true" && !seen_modal_base_image_ref) {
print "MODAL_BASE_IMAGE_REF=" modal_base_image_ref
}
if (!seen_trpc) {
- print "TRPC_URL=" trpc_url
+ print "R_TRPC_URL=" trpc_url
}
}
' .env > "$tmp_env"
diff --git a/docker-compose.compute-docker.yml b/docker-compose.compute-docker.yml
index 0abecea8..4aa78a20 100644
--- a/docker-compose.compute-docker.yml
+++ b/docker-compose.compute-docker.yml
@@ -2,31 +2,31 @@ name: roomote
services:
worker-image:
- image: ${DOCKER_WORKER_IMAGE:-roomote-worker:local}
+ image: ${R_DOCKER_WORKER_IMAGE:-roomote-worker:local}
container_name: roomote-worker-image
build:
context: .
dockerfile: apps/worker/Dockerfile
- platform: ${DOCKER_WORKER_PLATFORM:-linux/amd64}
+ platform: ${R_DOCKER_WORKER_PLATFORM:-linux/amd64}
command: ['true']
restart: 'no'
controller:
environment:
- DOCKER_WORKER_IMAGE: ${DOCKER_WORKER_IMAGE:-roomote-worker:local}
- DOCKER_WORKER_PLATFORM: ${DOCKER_WORKER_PLATFORM:-linux/amd64}
- DOCKER_WORKER_NETWORK: ${DOCKER_WORKER_NETWORK:-roomote_worker}
- DOCKER_WORKER_RELEASE_PATH: ${DOCKER_WORKER_RELEASE_PATH:-/roomote/releases/worker-current.tar.gz}
- DOCKER_WORKER_CPU_LIMIT: ${DOCKER_WORKER_CPU_LIMIT:-2}
- DOCKER_WORKER_MEMORY_LIMIT: ${DOCKER_WORKER_MEMORY_LIMIT:-4g}
- DOCKER_WORKER_PIDS_LIMIT: ${DOCKER_WORKER_PIDS_LIMIT:-512}
- DOCKER_WORKER_DISK_LIMIT: ${DOCKER_WORKER_DISK_LIMIT:-20g}
- DOCKER_WORKER_ALLOW_UNBOUNDED_DISK: ${DOCKER_WORKER_ALLOW_UNBOUNDED_DISK:-false}
- DOCKER_WORKER_LOG_MAX_SIZE: ${DOCKER_WORKER_LOG_MAX_SIZE:-10m}
- DOCKER_WORKER_LOG_MAX_FILES: ${DOCKER_WORKER_LOG_MAX_FILES:-3}
- DOCKER_WORKER_EGRESS_POLICY: ${DOCKER_WORKER_EGRESS_POLICY:-internet}
- DOCKER_STANDBY_MAX_COUNT: ${DOCKER_STANDBY_MAX_COUNT:-10}
- DOCKER_STANDBY_MAX_AGE_HOURS: ${DOCKER_STANDBY_MAX_AGE_HOURS:-24}
+ R_DOCKER_WORKER_IMAGE: ${R_DOCKER_WORKER_IMAGE:-roomote-worker:local}
+ R_DOCKER_WORKER_PLATFORM: ${R_DOCKER_WORKER_PLATFORM:-linux/amd64}
+ R_DOCKER_WORKER_NETWORK: ${R_DOCKER_WORKER_NETWORK:-roomote_worker}
+ R_DOCKER_WORKER_RELEASE_PATH: ${R_DOCKER_WORKER_RELEASE_PATH:-/roomote/releases/worker-current.tar.gz}
+ R_DOCKER_WORKER_CPU_LIMIT: ${R_DOCKER_WORKER_CPU_LIMIT:-2}
+ R_DOCKER_WORKER_MEMORY_LIMIT: ${R_DOCKER_WORKER_MEMORY_LIMIT:-4g}
+ R_DOCKER_WORKER_PIDS_LIMIT: ${R_DOCKER_WORKER_PIDS_LIMIT:-512}
+ R_DOCKER_WORKER_DISK_LIMIT: ${R_DOCKER_WORKER_DISK_LIMIT:-20g}
+ R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK: ${R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK:-false}
+ R_DOCKER_WORKER_LOG_MAX_SIZE: ${R_DOCKER_WORKER_LOG_MAX_SIZE:-10m}
+ R_DOCKER_WORKER_LOG_MAX_FILES: ${R_DOCKER_WORKER_LOG_MAX_FILES:-3}
+ R_DOCKER_WORKER_EGRESS_POLICY: ${R_DOCKER_WORKER_EGRESS_POLICY:-internet}
+ R_DOCKER_STANDBY_MAX_COUNT: ${R_DOCKER_STANDBY_MAX_COUNT:-10}
+ R_DOCKER_STANDBY_MAX_AGE_HOURS: ${R_DOCKER_STANDBY_MAX_AGE_HOURS:-24}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
depends_on:
@@ -35,7 +35,7 @@ services:
bullmq:
environment:
- DOCKER_STANDBY_MAX_COUNT: ${DOCKER_STANDBY_MAX_COUNT:-10}
- DOCKER_STANDBY_MAX_AGE_HOURS: ${DOCKER_STANDBY_MAX_AGE_HOURS:-24}
+ R_DOCKER_STANDBY_MAX_COUNT: ${R_DOCKER_STANDBY_MAX_COUNT:-10}
+ R_DOCKER_STANDBY_MAX_AGE_HOURS: ${R_DOCKER_STANDBY_MAX_AGE_HOURS:-24}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
diff --git a/docker-compose.production.yml b/docker-compose.production.yml
index 16cb1991..0481865e 100644
--- a/docker-compose.production.yml
+++ b/docker-compose.production.yml
@@ -21,10 +21,10 @@ x-roomote-production-env: &roomote-production-env
DASHBOARD_PASSWORD: ${DASHBOARD_PASSWORD:?DASHBOARD_PASSWORD is required}
R_APP_URL: https://${ROOMOTE_APP_DOMAIN:?ROOMOTE_APP_DOMAIN is required}
R_PUBLIC_URL: https://${ROOMOTE_APP_DOMAIN:?ROOMOTE_APP_DOMAIN is required}
- TRPC_URL: http://api:3001
- PREVIEW_PROXY_BASE_URL: https://${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
+ R_TRPC_URL: http://api:3001
+ R_PREVIEW_PROXY_BASE_URL: https://${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
NEXT_PUBLIC_PREVIEW_PROXY_BASE_URL: https://${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
- PREVIEW_DOMAINS: ${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
+ R_PREVIEW_DOMAINS: ${ROOMOTE_PREVIEW_DOMAIN:?ROOMOTE_PREVIEW_DOMAIN is required}
R_ALLOWED_EMAILS: ${R_ALLOWED_EMAILS:-}
R_MODEL: ${R_MODEL:-}
R_SMALL_MODEL: ${R_SMALL_MODEL:-}
@@ -56,7 +56,7 @@ x-roomote-production-env: &roomote-production-env
R_GITHUB_CLIENT_ID: ${R_GITHUB_CLIENT_ID:-}
R_GITHUB_CLIENT_SECRET: ${R_GITHUB_CLIENT_SECRET:-}
R_GITHUB_WEBHOOK_SECRET: ${R_GITHUB_WEBHOOK_SECRET:-}
- SLACK_APP_ID: ${SLACK_APP_ID:-}
+ R_SLACK_APP_ID: ${R_SLACK_APP_ID:-}
R_SLACK_CLIENT_ID: ${R_SLACK_CLIENT_ID:-}
R_SLACK_CLIENT_SECRET: ${R_SLACK_CLIENT_SECRET:-}
R_SLACK_SIGNING_SECRET: ${R_SLACK_SIGNING_SECRET:-}
@@ -65,7 +65,7 @@ x-roomote-production-env: &roomote-production-env
R_LINEAR_CLIENT_ID: ${R_LINEAR_CLIENT_ID:-}
R_LINEAR_CLIENT_SECRET: ${R_LINEAR_CLIENT_SECRET:-}
R_LINEAR_WEBHOOK_SECRET: ${R_LINEAR_WEBHOOK_SECRET:-}
- DEFAULT_COMPUTE_PROVIDER: ${DEFAULT_COMPUTE_PROVIDER:?DEFAULT_COMPUTE_PROVIDER is required}
+ R_DEFAULT_COMPUTE_PROVIDER: ${R_DEFAULT_COMPUTE_PROVIDER:?R_DEFAULT_COMPUTE_PROVIDER is required}
MODAL_TOKEN_ID: ${MODAL_TOKEN_ID:-}
MODAL_TOKEN_SECRET: ${MODAL_TOKEN_SECRET:-}
MODAL_ENDPOINT: ${MODAL_ENDPOINT:-}
@@ -79,10 +79,10 @@ x-roomote-production-env: &roomote-production-env
BL_WORKSPACE: ${BL_WORKSPACE:-}
BLAXEL_IMAGE: ${BLAXEL_IMAGE:-}
BLAXEL_REGION: ${BLAXEL_REGION:-}
- BLAXEL_STANDBY_MAX_COUNT: ${BLAXEL_STANDBY_MAX_COUNT:-25}
- BLAXEL_STANDBY_MAX_AGE_HOURS: ${BLAXEL_STANDBY_MAX_AGE_HOURS:-168}
- DOCKER_STANDBY_MAX_COUNT: ${DOCKER_STANDBY_MAX_COUNT:-10}
- DOCKER_STANDBY_MAX_AGE_HOURS: ${DOCKER_STANDBY_MAX_AGE_HOURS:-24}
+ R_BLAXEL_STANDBY_MAX_COUNT: ${R_BLAXEL_STANDBY_MAX_COUNT:-25}
+ R_BLAXEL_STANDBY_MAX_AGE_HOURS: ${R_BLAXEL_STANDBY_MAX_AGE_HOURS:-168}
+ R_DOCKER_STANDBY_MAX_COUNT: ${R_DOCKER_STANDBY_MAX_COUNT:-10}
+ R_DOCKER_STANDBY_MAX_AGE_HOURS: ${R_DOCKER_STANDBY_MAX_AGE_HOURS:-24}
x-roomote-production-build-args: &roomote-production-build-args
R_APP_ENV: production
diff --git a/docker-compose.self-host.yml b/docker-compose.self-host.yml
index 765758d5..f3c4ed0a 100644
--- a/docker-compose.self-host.yml
+++ b/docker-compose.self-host.yml
@@ -20,9 +20,9 @@ x-roomote-env: &roomote-env
S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:-roomote-local-artifacts-password}
S3_BUCKET_ARTIFACTS: ${S3_BUCKET_ARTIFACTS:-roomote-artifacts}
R_APP_URL: ${R_PUBLIC_URL:-http://localhost:13000}
- TRPC_URL: http://api:3001
- PREVIEW_PROXY_BASE_URL: ${PREVIEW_PROXY_BASE_URL:-http://localhost:18081}
- PREVIEW_DOMAINS: ${PREVIEW_DOMAINS:-localhost,127.0.0.1,roomotepreview.localhost}
+ R_TRPC_URL: http://api:3001
+ R_PREVIEW_PROXY_BASE_URL: ${R_PREVIEW_PROXY_BASE_URL:-http://localhost:18081}
+ R_PREVIEW_DOMAINS: ${R_PREVIEW_DOMAINS:-localhost,127.0.0.1,roomotepreview.localhost}
R_PUBLIC_URL: ${R_PUBLIC_URL:-}
R_ALLOWED_EMAILS: ${R_ALLOWED_EMAILS:-}
R_MODEL: ${R_MODEL:-}
@@ -53,7 +53,7 @@ x-roomote-env: &roomote-env
R_GITHUB_CLIENT_ID: ${R_GITHUB_CLIENT_ID:-}
R_GITHUB_CLIENT_SECRET: ${R_GITHUB_CLIENT_SECRET:-}
R_GITHUB_WEBHOOK_SECRET: ${R_GITHUB_WEBHOOK_SECRET:-}
- SLACK_APP_ID: ${SLACK_APP_ID:-}
+ R_SLACK_APP_ID: ${R_SLACK_APP_ID:-}
R_SLACK_CLIENT_ID: ${R_SLACK_CLIENT_ID:-}
R_SLACK_CLIENT_SECRET: ${R_SLACK_CLIENT_SECRET:-}
R_SLACK_SIGNING_SECRET: ${R_SLACK_SIGNING_SECRET:-}
@@ -64,15 +64,15 @@ x-roomote-env: &roomote-env
R_LINEAR_WEBHOOK_SECRET: ${R_LINEAR_WEBHOOK_SECRET:-}
R_GITHUB_APP_SLUG: ${R_GITHUB_APP_SLUG:-}
SETUP_TOKEN: ${SETUP_TOKEN:-}
- DEFAULT_COMPUTE_PROVIDER: ${DEFAULT_COMPUTE_PROVIDER:-docker}
+ R_DEFAULT_COMPUTE_PROVIDER: ${R_DEFAULT_COMPUTE_PROVIDER:-docker}
BL_API_KEY: ${BL_API_KEY:-}
BL_WORKSPACE: ${BL_WORKSPACE:-}
BLAXEL_IMAGE: ${BLAXEL_IMAGE:-}
BLAXEL_REGION: ${BLAXEL_REGION:-}
- BLAXEL_STANDBY_MAX_COUNT: ${BLAXEL_STANDBY_MAX_COUNT:-25}
- BLAXEL_STANDBY_MAX_AGE_HOURS: ${BLAXEL_STANDBY_MAX_AGE_HOURS:-168}
- DOCKER_STANDBY_MAX_COUNT: ${DOCKER_STANDBY_MAX_COUNT:-10}
- DOCKER_STANDBY_MAX_AGE_HOURS: ${DOCKER_STANDBY_MAX_AGE_HOURS:-24}
+ R_BLAXEL_STANDBY_MAX_COUNT: ${R_BLAXEL_STANDBY_MAX_COUNT:-25}
+ R_BLAXEL_STANDBY_MAX_AGE_HOURS: ${R_BLAXEL_STANDBY_MAX_AGE_HOURS:-168}
+ R_DOCKER_STANDBY_MAX_COUNT: ${R_DOCKER_STANDBY_MAX_COUNT:-10}
+ R_DOCKER_STANDBY_MAX_AGE_HOURS: ${R_DOCKER_STANDBY_MAX_AGE_HOURS:-24}
# Declarative environment provisioning: point ROOMOTE_ENVIRONMENTS_DIR at a
# directory of environment YAML files mounted into the api container (for
# example `- ./environments:/roomote/environments:ro` on the api service),
@@ -144,7 +144,7 @@ services:
container_name: roomote-api
image: *roomote-app-image
command: ['api']
- # Reachable from task worker containers (worker -> API over TRPC_URL).
+ # Reachable from task worker containers (worker -> API over R_TRPC_URL).
networks: [default, worker]
environment:
<<: *roomote-env
diff --git a/ecosystem.config.js b/ecosystem.config.js
index 43b55ad8..e81bfac6 100644
--- a/ecosystem.config.js
+++ b/ecosystem.config.js
@@ -56,19 +56,20 @@ const openCodeEnv = Object.fromEntries(
const defaultEnv = {
PORT: undefined,
USE_WORKER_RELEASE: process.env.USE_WORKER_RELEASE,
- DEFAULT_COMPUTE_PROVIDER: process.env.DEFAULT_COMPUTE_PROVIDER || 'docker',
- DOCKER_WORKER_IMAGE:
- process.env.DOCKER_WORKER_IMAGE || 'roomote-worker:local',
+ R_DEFAULT_COMPUTE_PROVIDER:
+ process.env.R_DEFAULT_COMPUTE_PROVIDER || 'docker',
+ R_DOCKER_WORKER_IMAGE:
+ process.env.R_DOCKER_WORKER_IMAGE || 'roomote-worker:local',
// Host arch, so Apple Silicon dev machines run the worker natively.
- DOCKER_WORKER_PLATFORM:
- process.env.DOCKER_WORKER_PLATFORM ||
+ R_DOCKER_WORKER_PLATFORM:
+ process.env.R_DOCKER_WORKER_PLATFORM ||
(process.arch === 'arm64' ? 'linux/arm64' : 'linux/amd64'),
R_PUBLIC_URL: publicUrl,
R_APP_URL: publicUrl,
// Set by `pnpm dev` to `/_roomote-api` so workers on hosted
// compute providers reach the API through the Caddy dev edge, matching
- // the deployed TRPC_URL contract.
- TRPC_URL: process.env.TRPC_URL,
+ // the deployed R_TRPC_URL contract.
+ R_TRPC_URL: process.env.R_TRPC_URL,
// GitHub app credentials:
R_GITHUB_APP_SLUG: process.env.R_GITHUB_APP_SLUG,
@@ -79,11 +80,13 @@ const defaultEnv = {
R_GITHUB_WEBHOOK_SECRET: process.env.R_GITHUB_WEBHOOK_SECRET,
// Slack app credentials:
- SLACK_APP_ID: process.env.SLACK_APP_ID,
+ R_SLACK_APP_ID: process.env.R_SLACK_APP_ID,
R_SLACK_CLIENT_ID: process.env.R_SLACK_CLIENT_ID,
R_SLACK_CLIENT_SECRET: process.env.R_SLACK_CLIENT_SECRET,
- SLACK_REDIRECT_URI: publicUrl ? `${publicUrl}/api/slack/callback` : undefined,
- SLACK_AUTH_URI: publicUrl ? `${publicUrl}/api/slack/auth` : undefined,
+ R_SLACK_REDIRECT_URI: publicUrl
+ ? `${publicUrl}/api/slack/callback`
+ : undefined,
+ R_SLACK_AUTH_URI: publicUrl ? `${publicUrl}/api/slack/auth` : undefined,
R_SLACK_SIGNING_SECRET: process.env.R_SLACK_SIGNING_SECRET,
// Linear app credentials:
diff --git a/packages/ado/src/__tests__/api.test.ts b/packages/ado/src/__tests__/api.test.ts
index 1efef1bd..0df42e4f 100644
--- a/packages/ado/src/__tests__/api.test.ts
+++ b/packages/ado/src/__tests__/api.test.ts
@@ -92,17 +92,17 @@ function makeTaskRun(payload: TaskRun['payload']): TaskRun {
describe('Azure DevOps API helpers', () => {
const originalAdoToken = process.env.ADO_TOKEN;
- const originalAdoOrganization = process.env.ADO_ORGANIZATION;
- const originalAdoBaseUrl = process.env.ADO_BASE_URL;
- const originalAdoUsername = process.env.ADO_USERNAME;
+ const originalAdoOrganization = process.env.R_ADO_ORGANIZATION;
+ const originalAdoBaseUrl = process.env.R_ADO_BASE_URL;
+ const originalAdoUsername = process.env.R_ADO_USERNAME;
beforeEach(() => {
vi.clearAllMocks();
clearAdoDeploymentUserCache();
process.env.ADO_TOKEN = 'ado_deployment_token';
- process.env.ADO_ORGANIZATION = 'acme';
- delete process.env.ADO_BASE_URL;
- delete process.env.ADO_USERNAME;
+ process.env.R_ADO_ORGANIZATION = 'acme';
+ delete process.env.R_ADO_BASE_URL;
+ delete process.env.R_ADO_USERNAME;
mockEnvironmentVariablesFindMany.mockResolvedValue([]);
mockEnvironmentsFindFirst.mockResolvedValue(null);
mockRepositoriesFindMany.mockResolvedValue([
@@ -121,21 +121,21 @@ describe('Azure DevOps API helpers', () => {
}
if (originalAdoOrganization === undefined) {
- delete process.env.ADO_ORGANIZATION;
+ delete process.env.R_ADO_ORGANIZATION;
} else {
- process.env.ADO_ORGANIZATION = originalAdoOrganization;
+ process.env.R_ADO_ORGANIZATION = originalAdoOrganization;
}
if (originalAdoBaseUrl === undefined) {
- delete process.env.ADO_BASE_URL;
+ delete process.env.R_ADO_BASE_URL;
} else {
- process.env.ADO_BASE_URL = originalAdoBaseUrl;
+ process.env.R_ADO_BASE_URL = originalAdoBaseUrl;
}
if (originalAdoUsername === undefined) {
- delete process.env.ADO_USERNAME;
+ delete process.env.R_ADO_USERNAME;
} else {
- process.env.ADO_USERNAME = originalAdoUsername;
+ process.env.R_ADO_USERNAME = originalAdoUsername;
}
});
@@ -221,7 +221,7 @@ describe('Azure DevOps API helpers', () => {
organization: '',
}),
).rejects.toThrow(
- 'ADO_ORGANIZATION is required to sync Azure DevOps repositories.',
+ 'R_ADO_ORGANIZATION is required to sync Azure DevOps repositories.',
);
});
diff --git a/packages/ado/src/api.ts b/packages/ado/src/api.ts
index d54a82d8..74e963d6 100644
--- a/packages/ado/src/api.ts
+++ b/packages/ado/src/api.ts
@@ -164,7 +164,7 @@ function normalizeBaseUrl(baseUrl: string): string {
const trimmed = baseUrl.trim().replace(/\/+$/, '');
if (!trimmed) {
- throw new Error('ADO_BASE_URL cannot be empty.');
+ throw new Error('R_ADO_BASE_URL cannot be empty.');
}
return new URL(trimmed).toString().replace(/\/+$/, '');
@@ -175,7 +175,7 @@ function normalizeOrganization(organization: string): string {
if (!trimmed) {
throw new Error(
- 'ADO_ORGANIZATION is required to sync Azure DevOps repositories.',
+ 'R_ADO_ORGANIZATION is required to sync Azure DevOps repositories.',
);
}
@@ -187,17 +187,17 @@ export async function resolveAdoToken(): Promise {
}
export async function resolveAdoOrganization(): Promise {
- const organization = await resolveDeploymentEnvVar('ADO_ORGANIZATION');
+ const organization = await resolveDeploymentEnvVar('R_ADO_ORGANIZATION');
return organization ? normalizeOrganization(organization) : null;
}
export async function resolveAdoBaseUrl(): Promise {
- const baseUrl = await resolveDeploymentEnvVar('ADO_BASE_URL');
+ const baseUrl = await resolveDeploymentEnvVar('R_ADO_BASE_URL');
return normalizeBaseUrl(baseUrl ?? DEFAULT_ADO_BASE_URL);
}
export async function resolveAdoUsername(): Promise {
- return resolveDeploymentEnvVar('ADO_USERNAME');
+ return resolveDeploymentEnvVar('R_ADO_USERNAME');
}
export function buildAdoOrganizationApiBaseUrl({
@@ -436,7 +436,7 @@ export async function getAdoPullRequest({
if (!resolvedOrganizationApiBaseUrl) {
throw new Error(
- 'ADO_ORGANIZATION is required to read Azure DevOps pull requests.',
+ 'R_ADO_ORGANIZATION is required to read Azure DevOps pull requests.',
);
}
@@ -587,7 +587,7 @@ export async function createAdoPullRequestComment({
if (!resolvedOrganizationApiBaseUrl) {
throw new Error(
- 'ADO_ORGANIZATION is required to create Azure DevOps pull request comments.',
+ 'R_ADO_ORGANIZATION is required to create Azure DevOps pull request comments.',
);
}
@@ -657,7 +657,7 @@ export async function listAdoRepositories({
if (!resolvedOrganization?.trim()) {
throw new Error(
- 'ADO_ORGANIZATION is required to sync Azure DevOps repositories.',
+ 'R_ADO_ORGANIZATION is required to sync Azure DevOps repositories.',
);
}
@@ -953,7 +953,7 @@ export async function ensureAdoServiceHooksForRepositories({
if (!resolvedOrganization?.trim()) {
throw new Error(
- 'ADO_ORGANIZATION is required to configure Azure DevOps service hooks.',
+ 'R_ADO_ORGANIZATION is required to configure Azure DevOps service hooks.',
);
}
@@ -1097,7 +1097,7 @@ export async function removeAdoServiceHooksForRepositories({
if (!resolvedOrganization?.trim()) {
throw new Error(
- 'ADO_ORGANIZATION is required to configure Azure DevOps service hooks.',
+ 'R_ADO_ORGANIZATION is required to configure Azure DevOps service hooks.',
);
}
@@ -1248,7 +1248,7 @@ export async function syncAdoRepositories({
if (!resolvedOrganization?.trim()) {
throw new Error(
- 'ADO_ORGANIZATION is required to sync Azure DevOps repositories.',
+ 'R_ADO_ORGANIZATION is required to sync Azure DevOps repositories.',
);
}
diff --git a/packages/auth/src/__tests__/sandbox-oidc.test.ts b/packages/auth/src/__tests__/sandbox-oidc.test.ts
index 44517d3a..0dec6f68 100644
--- a/packages/auth/src/__tests__/sandbox-oidc.test.ts
+++ b/packages/auth/src/__tests__/sandbox-oidc.test.ts
@@ -15,7 +15,7 @@ vi.mock('@roomote/env', () => ({
Env: {
NODE_ENV: 'test',
R_APP_URL: 'https://app.roomote.test',
- TRPC_URL: 'https://app.roomote.test/_roomote-api/',
+ R_TRPC_URL: 'https://app.roomote.test/_roomote-api/',
},
}));
diff --git a/packages/auth/src/sandbox-oidc.ts b/packages/auth/src/sandbox-oidc.ts
index 31130f46..344ebe66 100644
--- a/packages/auth/src/sandbox-oidc.ts
+++ b/packages/auth/src/sandbox-oidc.ts
@@ -43,7 +43,7 @@ export function getSandboxOidcIssuer(): string {
// This issuer URL is configured in customer IAM/OIDC provider trust policies,
// so treat it as stable once deployed. It must point at apps/api, whose host
// serves the root .well-known discovery endpoint and advertised JWKS URI.
- return normalizeUrlBase(Env.TRPC_URL);
+ return normalizeUrlBase(Env.R_TRPC_URL);
}
export function getSandboxOidcKeyId(
diff --git a/packages/bitbucket/src/api.ts b/packages/bitbucket/src/api.ts
index 5229b473..b2734ab6 100644
--- a/packages/bitbucket/src/api.ts
+++ b/packages/bitbucket/src/api.ts
@@ -188,7 +188,7 @@ function normalizeBaseUrl(baseUrl: string): string {
const trimmed = baseUrl.trim().replace(/\/+$/, '');
if (!trimmed) {
- throw new Error('BITBUCKET_BASE_URL cannot be empty.');
+ throw new Error('R_BITBUCKET_BASE_URL cannot be empty.');
}
return new URL(trimmed).toString().replace(/\/+$/, '');
@@ -264,14 +264,14 @@ export async function resolveBitbucketToken(): Promise {
}
export async function resolveBitbucketBaseUrl(): Promise {
- const baseUrl = await resolveDeploymentEnvVar('BITBUCKET_BASE_URL');
+ const baseUrl = await resolveDeploymentEnvVar('R_BITBUCKET_BASE_URL');
return baseUrl?.trim()
? normalizeBaseUrl(baseUrl)
: DEFAULT_BITBUCKET_BASE_URL;
}
export async function resolveBitbucketUsername(): Promise {
- return resolveDeploymentEnvVar('BITBUCKET_USERNAME');
+ return resolveDeploymentEnvVar('R_BITBUCKET_USERNAME');
}
let cachedBitbucketDeploymentUser: {
@@ -402,7 +402,7 @@ async function resolveAuthIdentity({
// email. There is no discovery endpoint that works without it, so the value
// must be configured up front.
throw new Error(
- 'BITBUCKET_USERNAME is required with BITBUCKET_TOKEN. Set it to the Atlassian account email that owns the API token.',
+ 'R_BITBUCKET_USERNAME is required with BITBUCKET_TOKEN. Set it to the Atlassian account email that owns the API token.',
);
}
diff --git a/packages/cloud-agents/src/server/__tests__/shared-utils.test.ts b/packages/cloud-agents/src/server/__tests__/shared-utils.test.ts
index a3be62d9..112895ae 100644
--- a/packages/cloud-agents/src/server/__tests__/shared-utils.test.ts
+++ b/packages/cloud-agents/src/server/__tests__/shared-utils.test.ts
@@ -4,7 +4,7 @@ vi.mock('@roomote/env', async (importOriginal) => {
return {
...actual,
Env: {
- TRPC_URL: 'https://api.example.com/',
+ R_TRPC_URL: 'https://api.example.com/',
R_APP_URL: 'https://app.example.com/',
},
};
diff --git a/packages/cloud-agents/src/server/shared-utils.ts b/packages/cloud-agents/src/server/shared-utils.ts
index 40ada1ee..a0e3fc4d 100644
--- a/packages/cloud-agents/src/server/shared-utils.ts
+++ b/packages/cloud-agents/src/server/shared-utils.ts
@@ -16,7 +16,7 @@ export function normalizeApiBaseUrl(raw: string | undefined): string | null {
export function resolveApiBaseUrl(explicit?: string): string | null {
return (
normalizeApiBaseUrl(explicit) ??
- normalizeApiBaseUrl(Env.TRPC_URL) ??
+ normalizeApiBaseUrl(Env.R_TRPC_URL) ??
normalizeApiBaseUrl(Env.R_APP_URL)
);
}
diff --git a/packages/communication/evals/run-telegram-scenario.ts b/packages/communication/evals/run-telegram-scenario.ts
index 193c6366..a8a1f54c 100644
--- a/packages/communication/evals/run-telegram-scenario.ts
+++ b/packages/communication/evals/run-telegram-scenario.ts
@@ -6,7 +6,7 @@
* scripts/judge-criteria.ts).
*
* Prerequisites: local dev stack running (pnpm dev) with
- * TELEGRAM_API_BASE_URL pointing at the mock port used here, and the mock
+ * R_TELEGRAM_API_BASE_URL pointing at the mock port used here, and the mock
* Telegram user mapping seeded (see
* .agents/skills/mock-telegram-testing/SKILL.md).
*
@@ -109,11 +109,11 @@ const baseUrl = await server.start(Number.parseInt(values.port!, 10));
console.log(`mock telegram listening at ${baseUrl} -> ${values.webhook}`);
if (
- process.env.TELEGRAM_API_BASE_URL &&
- process.env.TELEGRAM_API_BASE_URL.replace(/\/+$/, '') !== baseUrl
+ process.env.R_TELEGRAM_API_BASE_URL &&
+ process.env.R_TELEGRAM_API_BASE_URL.replace(/\/+$/, '') !== baseUrl
) {
console.warn(
- `WARNING: TELEGRAM_API_BASE_URL=${process.env.TELEGRAM_API_BASE_URL} does not match ${baseUrl}; ` +
+ `WARNING: R_TELEGRAM_API_BASE_URL=${process.env.R_TELEGRAM_API_BASE_URL} does not match ${baseUrl}; ` +
`the API server may post replies somewhere else.`,
);
}
diff --git a/packages/communication/scripts/run-mock-telegram.ts b/packages/communication/scripts/run-mock-telegram.ts
index 4f5eaee0..7fc7aaac 100644
--- a/packages/communication/scripts/run-mock-telegram.ts
+++ b/packages/communication/scripts/run-mock-telegram.ts
@@ -212,7 +212,7 @@ async function main() {
console.info(`Mock Telegram Bot API listening at ${baseUrl}`);
console.info(
- `Set TELEGRAM_API_BASE_URL=${baseUrl} in the Roomote services you want to point at the harness.`,
+ `Set R_TELEGRAM_API_BASE_URL=${baseUrl} in the Roomote services you want to point at the harness.`,
);
if (config.replay?.length) {
diff --git a/packages/communication/src/__tests__/thread-reply-footer-context.test.ts b/packages/communication/src/__tests__/thread-reply-footer-context.test.ts
index 5f3663ce..bd98e4ec 100644
--- a/packages/communication/src/__tests__/thread-reply-footer-context.test.ts
+++ b/packages/communication/src/__tests__/thread-reply-footer-context.test.ts
@@ -42,8 +42,8 @@ vi.mock('@roomote/db/server', () => ({
vi.mock('@roomote/env', () => ({
Env: {
- PREVIEW_PROXY_BASE_URL: 'https://preview.example.com',
- PREVIEW_DOMAINS: 'preview.example.com',
+ R_PREVIEW_PROXY_BASE_URL: 'https://preview.example.com',
+ R_PREVIEW_DOMAINS: 'preview.example.com',
},
}));
diff --git a/packages/communication/src/telegram-api-base-url.ts b/packages/communication/src/telegram-api-base-url.ts
index cea86a6b..b1b2ae10 100644
--- a/packages/communication/src/telegram-api-base-url.ts
+++ b/packages/communication/src/telegram-api-base-url.ts
@@ -4,13 +4,13 @@ const DEFAULT_TELEGRAM_API_BASE_URL = 'https://api.telegram.org';
/**
* Resolves the Telegram Bot API host, mirroring `getSlackApiBaseUrl` in
- * `@roomote/slack`: `TELEGRAM_API_BASE_URL` lets tests and the mock Telegram
+ * `@roomote/slack`: `R_TELEGRAM_API_BASE_URL` lets tests and the mock Telegram
* harness reroute every outbound Bot API call without touching call sites.
*/
export function getTelegramApiBaseUrl(): string {
const configuredUrl = (
- process.env.TELEGRAM_API_BASE_URL ??
- Env.TELEGRAM_API_BASE_URL ??
+ process.env.R_TELEGRAM_API_BASE_URL ??
+ Env.R_TELEGRAM_API_BASE_URL ??
DEFAULT_TELEGRAM_API_BASE_URL
).trim();
diff --git a/packages/communication/src/thread-reply-footer-context.ts b/packages/communication/src/thread-reply-footer-context.ts
index 91505cf8..1787086f 100644
--- a/packages/communication/src/thread-reply-footer-context.ts
+++ b/packages/communication/src/thread-reply-footer-context.ts
@@ -148,8 +148,8 @@ export async function resolveThreadReplyLivePreviewUrl(
)?.initial_path;
const previewRuntimeConfig = await resolveEffectivePreviewRuntimeConfig({
- defaultPreviewProxyBaseUrl: Env.PREVIEW_PROXY_BASE_URL,
- defaultPreviewDomains: Env.PREVIEW_DOMAINS,
+ defaultPreviewProxyBaseUrl: Env.R_PREVIEW_PROXY_BASE_URL,
+ defaultPreviewDomains: Env.R_PREVIEW_DOMAINS,
});
const previewProxyBaseUrl =
previewRuntimeConfig.effective.previewProxyBaseUrl;
diff --git a/packages/compute-providers/src/__tests__/environment-machine.test.ts b/packages/compute-providers/src/__tests__/environment-machine.test.ts
index 2d1377d4..338117ef 100644
--- a/packages/compute-providers/src/__tests__/environment-machine.test.ts
+++ b/packages/compute-providers/src/__tests__/environment-machine.test.ts
@@ -9,20 +9,20 @@ import {
getNamedPortsForEnvironment,
} from '../environment-machine';
-const originalTrpcUrl = process.env.TRPC_URL;
+const originalTrpcUrl = process.env.R_TRPC_URL;
const originalRoomoteAppUrl = process.env.R_APP_URL;
describe('getNamedPortsForEnvironment', () => {
beforeEach(() => {
- delete process.env.TRPC_URL;
+ delete process.env.R_TRPC_URL;
delete process.env.R_APP_URL;
});
afterAll(() => {
if (originalTrpcUrl === undefined) {
- delete process.env.TRPC_URL;
+ delete process.env.R_TRPC_URL;
} else {
- process.env.TRPC_URL = originalTrpcUrl;
+ process.env.R_TRPC_URL = originalTrpcUrl;
}
if (originalRoomoteAppUrl === undefined) {
@@ -33,7 +33,7 @@ describe('getNamedPortsForEnvironment', () => {
});
it('does not infer callback surfaces from local controller callback URLs', () => {
- process.env.TRPC_URL = 'http://localhost:3001';
+ process.env.R_TRPC_URL = 'http://localhost:3001';
const namedPorts = getNamedPortsForEnvironment({});
diff --git a/packages/compute-providers/src/__tests__/factory.test.ts b/packages/compute-providers/src/__tests__/factory.test.ts
index 9cdf25a8..1b39551b 100644
--- a/packages/compute-providers/src/__tests__/factory.test.ts
+++ b/packages/compute-providers/src/__tests__/factory.test.ts
@@ -122,7 +122,7 @@ describe('createComputeProviderClient', () => {
it('falls back to the worker image for a missing Modal base image ref', () => {
const workerImageEnvKeys = [
'MODAL_BASE_IMAGE_REF',
- 'DOCKER_WORKER_IMAGE',
+ 'R_DOCKER_WORKER_IMAGE',
'RELEASE_VERSION',
'ROOMOTE_WORKER_IMAGE_REPO',
'APP_ENV',
@@ -144,7 +144,7 @@ describe('createComputeProviderClient', () => {
tokenSecret: 'token-secret',
} as ModalConfig,
envFallback: {
- DOCKER_WORKER_IMAGE: 'ghcr.io/roocodeinc/roomote-worker:v9.9.9',
+ R_DOCKER_WORKER_IMAGE: 'ghcr.io/roocodeinc/roomote-worker:v9.9.9',
},
});
@@ -177,7 +177,7 @@ describe('createComputeProviderClient', () => {
} as ModalConfig,
envFallback: {
NODE_ENV: 'development',
- DOCKER_WORKER_IMAGE: 'roomote-worker:local',
+ R_DOCKER_WORKER_IMAGE: 'roomote-worker:local',
},
});
@@ -391,7 +391,7 @@ describe('createComputeProviderClient', () => {
envFallback: {
BL_API_KEY: 'key',
BL_WORKSPACE: 'workspace',
- DOCKER_WORKER_IMAGE: 'ghcr.io/roomote/worker:v1',
+ R_DOCKER_WORKER_IMAGE: 'ghcr.io/roomote/worker:v1',
},
}),
).toThrow('Missing BLAXEL_IMAGE');
diff --git a/packages/compute-providers/src/factory.ts b/packages/compute-providers/src/factory.ts
index f18016b6..1bad11c9 100644
--- a/packages/compute-providers/src/factory.ts
+++ b/packages/compute-providers/src/factory.ts
@@ -59,14 +59,14 @@ export function createComputeProviderClient(
// The published worker image doubles as the Modal base image, so a
// missing ref falls back to the deployment's effective worker image
- // (explicit DOCKER_WORKER_IMAGE, or derived from the baked
+ // (explicit R_DOCKER_WORKER_IMAGE, or derived from the baked
// RELEASE_VERSION on published app images), then the development-only
// public latest image.
const baseImageRef =
options.config?.baseImageRef ??
resolveEffectiveModalBaseImageRef({
MODAL_BASE_IMAGE_REF: envValue('MODAL_BASE_IMAGE_REF'),
- DOCKER_WORKER_IMAGE: envValue('DOCKER_WORKER_IMAGE'),
+ R_DOCKER_WORKER_IMAGE: envValue('R_DOCKER_WORKER_IMAGE'),
RELEASE_VERSION: envValue('RELEASE_VERSION'),
ROOMOTE_WORKER_IMAGE_REPO: envValue('ROOMOTE_WORKER_IMAGE_REPO'),
APP_ENV: envValue('APP_ENV'),
@@ -203,7 +203,7 @@ export function createComputeProviderClient(
const image = options.config?.image ?? envValue('BLAXEL_IMAGE');
const region = envValue('BLAXEL_REGION');
const standbyMaxAgeHours = Number(
- envValue('BLAXEL_STANDBY_MAX_AGE_HOURS'),
+ envValue('R_BLAXEL_STANDBY_MAX_AGE_HOURS'),
);
assertDefined(apiKey, 'Missing BL_API_KEY');
diff --git a/packages/compute-providers/src/sandbox/worker-release-selection.ts b/packages/compute-providers/src/sandbox/worker-release-selection.ts
index 7b732ca9..d3282137 100644
--- a/packages/compute-providers/src/sandbox/worker-release-selection.ts
+++ b/packages/compute-providers/src/sandbox/worker-release-selection.ts
@@ -238,8 +238,8 @@ export function extractWorkerReleaseVersionFromArchivePath(
export function resolveWorkerReleaseSelectionFromEnv(
env: NodeJS.ProcessEnv = process.env,
): WorkerReleaseSelection {
- const rawChannel = env.WORKER_RELEASE_CHANNEL?.trim();
- const rawVersion = env.WORKER_RELEASE_VERSION?.trim();
+ const rawChannel = env.R_WORKER_RELEASE_CHANNEL?.trim();
+ const rawVersion = env.R_WORKER_RELEASE_VERSION?.trim();
let channel: WorkerReleaseChannel =
resolveAppEnv(env) === 'preview'
? 'preview'
@@ -248,7 +248,7 @@ export function resolveWorkerReleaseSelectionFromEnv(
if (rawChannel) {
if (!isWorkerReleaseChannel(rawChannel)) {
throw new Error(
- `Unsupported WORKER_RELEASE_CHANNEL: ${rawChannel}. Expected "stable" or "preview".`,
+ `Unsupported R_WORKER_RELEASE_CHANNEL: ${rawChannel}. Expected "stable" or "preview".`,
);
}
diff --git a/packages/compute-providers/src/worker-env/__tests__/base.test.ts b/packages/compute-providers/src/worker-env/__tests__/base.test.ts
index 3ea70d4a..2513cdab 100644
--- a/packages/compute-providers/src/worker-env/__tests__/base.test.ts
+++ b/packages/compute-providers/src/worker-env/__tests__/base.test.ts
@@ -1,7 +1,7 @@
vi.mock('@roomote/env', () => ({
Env: {
R_APP_URL: 'https://web.roomote.example.com',
- TRPC_URL: 'https://api.roomote.example.com',
+ R_TRPC_URL: 'https://api.roomote.example.com',
},
}));
@@ -12,7 +12,7 @@ describe('buildBaseWorkerEnv', () => {
beforeEach(() => {
process.env = { ...originalEnv };
- delete process.env.PREVIEW_PROXY_BASE_URL;
+ delete process.env.R_PREVIEW_PROXY_BASE_URL;
delete process.env.JOB_AUTH_PRIVATE_KEY;
delete process.env.JOB_AUTH_PUBLIC_KEY;
delete process.env.R_MODEL;
@@ -41,8 +41,8 @@ describe('buildBaseWorkerEnv', () => {
});
expect(env.R_APP_URL).toBe('https://web.roomote.example.com');
- expect(env.TRPC_URL).toBe('https://api.roomote.example.com');
- expect(env.PREVIEW_PROXY_BASE_URL).toBeUndefined();
+ expect(env.R_TRPC_URL).toBe('https://api.roomote.example.com');
+ expect(env.R_PREVIEW_PROXY_BASE_URL).toBeUndefined();
});
it('injects the legacy ROOMOTE_APP_URL alias for pre-rename snapshot workers', () => {
@@ -55,14 +55,14 @@ describe('buildBaseWorkerEnv', () => {
});
it('forwards an explicit preview proxy base URL', () => {
- process.env.PREVIEW_PROXY_BASE_URL = 'https://preview.example.com';
+ process.env.R_PREVIEW_PROXY_BASE_URL = 'https://preview.example.com';
const env = buildBaseWorkerEnv({
authToken: 'auth-token',
extraEnv: {},
});
- expect(env.PREVIEW_PROXY_BASE_URL).toBe('https://preview.example.com');
+ expect(env.R_PREVIEW_PROXY_BASE_URL).toBe('https://preview.example.com');
});
it('forwards only public launcher auth transport keys for the sandbox worker runtime', () => {
diff --git a/packages/compute-providers/src/worker-env/base.ts b/packages/compute-providers/src/worker-env/base.ts
index a79a564d..0ebfeb16 100644
--- a/packages/compute-providers/src/worker-env/base.ts
+++ b/packages/compute-providers/src/worker-env/base.ts
@@ -100,7 +100,7 @@ export function buildBaseWorkerEnv({
sandboxExpiresAtMs,
extraEnv,
}: BuildWorkerEnvOptions): Record {
- const previewProxyBaseUrl = process.env.PREVIEW_PROXY_BASE_URL;
+ const previewProxyBaseUrl = process.env.R_PREVIEW_PROXY_BASE_URL;
return {
AUTH_TOKEN: authToken,
@@ -124,7 +124,7 @@ export function buildBaseWorkerEnv({
// rename run a worker build that requires ROOMOTE_APP_URL at startup.
// Remove once pre-rename snapshots have aged out.
ROOMOTE_APP_URL: Env.R_APP_URL,
- TRPC_URL: Env.TRPC_URL,
+ R_TRPC_URL: Env.R_TRPC_URL,
SKIP_ENV_VALIDATION: '1',
// These are launcher-to-worker transport values. Keep them tied to the
// current process env instead of the shared Env snapshot because the worker
@@ -138,7 +138,7 @@ export function buildBaseWorkerEnv({
PREVIEW_AUTH_PUBLIC_KEY: process.env.PREVIEW_AUTH_PUBLIC_KEY,
}),
...(previewProxyBaseUrl && {
- PREVIEW_PROXY_BASE_URL: previewProxyBaseUrl,
+ R_PREVIEW_PROXY_BASE_URL: previewProxyBaseUrl,
}),
...(process.env.PREVIEW_PROXY_SUBDOMAIN_SUFFIX && {
PREVIEW_PROXY_SUBDOMAIN_SUFFIX:
diff --git a/packages/db/drizzle/0010_more_canonical_r_env_vars.sql b/packages/db/drizzle/0010_more_canonical_r_env_vars.sql
new file mode 100644
index 00000000..2f79ee9a
--- /dev/null
+++ b/packages/db/drizzle/0010_more_canonical_r_env_vars.sql
@@ -0,0 +1,228 @@
+WITH env_var_renames(old_name, new_name, priority) AS (
+ VALUES
+ ('DEFAULT_COMPUTE_PROVIDER', 'R_DEFAULT_COMPUTE_PROVIDER', 10),
+ ('EXCLUDED_COMPUTE_PROVIDERS', 'R_EXCLUDED_COMPUTE_PROVIDERS', 10),
+ ('DOCKER_WORKER_IMAGE', 'R_DOCKER_WORKER_IMAGE', 10),
+ ('DOCKER_WORKER_PLATFORM', 'R_DOCKER_WORKER_PLATFORM', 10),
+ ('DOCKER_WORKER_NETWORK', 'R_DOCKER_WORKER_NETWORK', 10),
+ ('DOCKER_WORKER_RELEASE_PATH', 'R_DOCKER_WORKER_RELEASE_PATH', 10),
+ ('DOCKER_WORKER_CPU_LIMIT', 'R_DOCKER_WORKER_CPU_LIMIT', 10),
+ ('DOCKER_WORKER_MEMORY_LIMIT', 'R_DOCKER_WORKER_MEMORY_LIMIT', 10),
+ ('DOCKER_WORKER_PIDS_LIMIT', 'R_DOCKER_WORKER_PIDS_LIMIT', 10),
+ ('DOCKER_WORKER_DISK_LIMIT', 'R_DOCKER_WORKER_DISK_LIMIT', 10),
+ ('DOCKER_WORKER_ALLOW_UNBOUNDED_DISK', 'R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK', 10),
+ ('DOCKER_WORKER_LOG_MAX_SIZE', 'R_DOCKER_WORKER_LOG_MAX_SIZE', 10),
+ ('DOCKER_WORKER_LOG_MAX_FILES', 'R_DOCKER_WORKER_LOG_MAX_FILES', 10),
+ ('DOCKER_WORKER_EGRESS_POLICY', 'R_DOCKER_WORKER_EGRESS_POLICY', 10),
+ ('DOCKER_STANDBY_MAX_COUNT', 'R_DOCKER_STANDBY_MAX_COUNT', 10),
+ ('DOCKER_STANDBY_MAX_AGE_HOURS', 'R_DOCKER_STANDBY_MAX_AGE_HOURS', 10),
+ ('BLAXEL_STANDBY_MAX_COUNT', 'R_BLAXEL_STANDBY_MAX_COUNT', 10),
+ ('BLAXEL_STANDBY_MAX_AGE_HOURS', 'R_BLAXEL_STANDBY_MAX_AGE_HOURS', 10),
+ ('TRPC_URL', 'R_TRPC_URL', 10),
+ ('PREVIEW_PROXY_BASE_URL', 'R_PREVIEW_PROXY_BASE_URL', 10),
+ ('PREVIEW_DOMAINS', 'R_PREVIEW_DOMAINS', 10),
+ ('PREVIEW_TOKEN_TTL_SECONDS', 'R_PREVIEW_TOKEN_TTL_SECONDS', 10),
+ ('SLACK_APP_ID', 'R_SLACK_APP_ID', 10),
+ ('SLACK_REDIRECT_URI', 'R_SLACK_REDIRECT_URI', 10),
+ ('SLACK_AUTH_URI', 'R_SLACK_AUTH_URI', 10),
+ ('SLACK_API_BASE_URL', 'R_SLACK_API_BASE_URL', 10),
+ ('SLACK_UNFURL_ALLOWED_DOMAINS', 'R_SLACK_UNFURL_ALLOWED_DOMAINS', 10),
+ ('SLACK_API_TIMEOUT_MS', 'R_SLACK_API_TIMEOUT_MS', 10),
+ ('SLACK_DEBUG_LOGS', 'R_SLACK_DEBUG_LOGS', 10),
+ ('ROUTER_DEBUG_CHANNEL_ID', 'R_ROUTER_DEBUG_CHANNEL_ID', 10),
+ ('TELEGRAM_API_BASE_URL', 'R_TELEGRAM_API_BASE_URL', 10),
+ ('GITHUB_MCP_SERVER_URL', 'R_GITHUB_MCP_SERVER_URL', 10),
+ ('GITHUB_AUTOMATED_SKIP_REPOS', 'R_GITHUB_AUTOMATED_SKIP_REPOS', 10),
+ ('GITHUB_AUTOMATED_SKIP_OWNERS', 'R_GITHUB_AUTOMATED_SKIP_OWNERS', 10),
+ ('GITLAB_BASE_URL', 'R_GITLAB_BASE_URL', 10),
+ ('GITLAB_CLIENT_ID', 'R_GITLAB_CLIENT_ID', 10),
+ ('GITLAB_CLIENT_SECRET', 'R_GITLAB_CLIENT_SECRET', 10),
+ ('GITLAB_WEBHOOK_SIGNING_TOKEN', 'R_GITLAB_WEBHOOK_SIGNING_TOKEN', 10),
+ ('GITLAB_WEBHOOK_SECRET', 'R_GITLAB_WEBHOOK_SECRET', 10),
+ ('GITEA_BASE_URL', 'R_GITEA_BASE_URL', 10),
+ ('GITEA_USERNAME', 'R_GITEA_USERNAME', 10),
+ ('GITEA_CLIENT_ID', 'R_GITEA_CLIENT_ID', 10),
+ ('GITEA_CLIENT_SECRET', 'R_GITEA_CLIENT_SECRET', 10),
+ ('GITEA_WEBHOOK_SECRET', 'R_GITEA_WEBHOOK_SECRET', 10),
+ ('ADO_ORGANIZATION', 'R_ADO_ORGANIZATION', 10),
+ ('ADO_BASE_URL', 'R_ADO_BASE_URL', 10),
+ ('ADO_USERNAME', 'R_ADO_USERNAME', 10),
+ ('ADO_CLIENT_ID', 'R_ADO_CLIENT_ID', 10),
+ ('ADO_CLIENT_SECRET', 'R_ADO_CLIENT_SECRET', 10),
+ ('ADO_TENANT_ID', 'R_ADO_TENANT_ID', 10),
+ ('ADO_WEBHOOK_SECRET', 'R_ADO_WEBHOOK_SECRET', 10),
+ ('BITBUCKET_USERNAME', 'R_BITBUCKET_USERNAME', 10),
+ ('BITBUCKET_BASE_URL', 'R_BITBUCKET_BASE_URL', 10),
+ ('BITBUCKET_CLIENT_ID', 'R_BITBUCKET_CLIENT_ID', 10),
+ ('BITBUCKET_CLIENT_SECRET', 'R_BITBUCKET_CLIENT_SECRET', 10),
+ ('BITBUCKET_WEBHOOK_SECRET', 'R_BITBUCKET_WEBHOOK_SECRET', 10),
+ ('API_DEBUG_LOGS', 'R_API_DEBUG_LOGS', 10),
+ ('WEBHOOK_RETENTION_DAYS', 'R_WEBHOOK_RETENTION_DAYS', 10),
+ ('API_EXTERNAL_REQUEST_TIMEOUT_MS', 'R_API_EXTERNAL_REQUEST_TIMEOUT_MS', 10),
+ ('API_SLOW_REQUEST_THRESHOLD_MS', 'R_API_SLOW_REQUEST_THRESHOLD_MS', 10),
+ ('API_SLOW_EXTERNAL_REQUEST_THRESHOLD_MS', 'R_API_SLOW_EXTERNAL_REQUEST_THRESHOLD_MS', 10),
+ ('WORKER_RELEASE_CHANNEL', 'R_WORKER_RELEASE_CHANNEL', 10),
+ ('WORKER_RELEASE_VERSION', 'R_WORKER_RELEASE_VERSION', 10)
+)
+DELETE FROM "environment_variables" legacy
+USING env_var_renames rename
+WHERE legacy."name" = rename.old_name
+ AND EXISTS (
+ SELECT 1
+ FROM "environment_variables" canonical
+ WHERE canonical."name" = rename.new_name
+ );
+
+WITH env_var_renames(old_name, new_name, priority) AS (
+ VALUES
+ ('DEFAULT_COMPUTE_PROVIDER', 'R_DEFAULT_COMPUTE_PROVIDER', 10),
+ ('EXCLUDED_COMPUTE_PROVIDERS', 'R_EXCLUDED_COMPUTE_PROVIDERS', 10),
+ ('DOCKER_WORKER_IMAGE', 'R_DOCKER_WORKER_IMAGE', 10),
+ ('DOCKER_WORKER_PLATFORM', 'R_DOCKER_WORKER_PLATFORM', 10),
+ ('DOCKER_WORKER_NETWORK', 'R_DOCKER_WORKER_NETWORK', 10),
+ ('DOCKER_WORKER_RELEASE_PATH', 'R_DOCKER_WORKER_RELEASE_PATH', 10),
+ ('DOCKER_WORKER_CPU_LIMIT', 'R_DOCKER_WORKER_CPU_LIMIT', 10),
+ ('DOCKER_WORKER_MEMORY_LIMIT', 'R_DOCKER_WORKER_MEMORY_LIMIT', 10),
+ ('DOCKER_WORKER_PIDS_LIMIT', 'R_DOCKER_WORKER_PIDS_LIMIT', 10),
+ ('DOCKER_WORKER_DISK_LIMIT', 'R_DOCKER_WORKER_DISK_LIMIT', 10),
+ ('DOCKER_WORKER_ALLOW_UNBOUNDED_DISK', 'R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK', 10),
+ ('DOCKER_WORKER_LOG_MAX_SIZE', 'R_DOCKER_WORKER_LOG_MAX_SIZE', 10),
+ ('DOCKER_WORKER_LOG_MAX_FILES', 'R_DOCKER_WORKER_LOG_MAX_FILES', 10),
+ ('DOCKER_WORKER_EGRESS_POLICY', 'R_DOCKER_WORKER_EGRESS_POLICY', 10),
+ ('DOCKER_STANDBY_MAX_COUNT', 'R_DOCKER_STANDBY_MAX_COUNT', 10),
+ ('DOCKER_STANDBY_MAX_AGE_HOURS', 'R_DOCKER_STANDBY_MAX_AGE_HOURS', 10),
+ ('BLAXEL_STANDBY_MAX_COUNT', 'R_BLAXEL_STANDBY_MAX_COUNT', 10),
+ ('BLAXEL_STANDBY_MAX_AGE_HOURS', 'R_BLAXEL_STANDBY_MAX_AGE_HOURS', 10),
+ ('TRPC_URL', 'R_TRPC_URL', 10),
+ ('PREVIEW_PROXY_BASE_URL', 'R_PREVIEW_PROXY_BASE_URL', 10),
+ ('PREVIEW_DOMAINS', 'R_PREVIEW_DOMAINS', 10),
+ ('PREVIEW_TOKEN_TTL_SECONDS', 'R_PREVIEW_TOKEN_TTL_SECONDS', 10),
+ ('SLACK_APP_ID', 'R_SLACK_APP_ID', 10),
+ ('SLACK_REDIRECT_URI', 'R_SLACK_REDIRECT_URI', 10),
+ ('SLACK_AUTH_URI', 'R_SLACK_AUTH_URI', 10),
+ ('SLACK_API_BASE_URL', 'R_SLACK_API_BASE_URL', 10),
+ ('SLACK_UNFURL_ALLOWED_DOMAINS', 'R_SLACK_UNFURL_ALLOWED_DOMAINS', 10),
+ ('SLACK_API_TIMEOUT_MS', 'R_SLACK_API_TIMEOUT_MS', 10),
+ ('SLACK_DEBUG_LOGS', 'R_SLACK_DEBUG_LOGS', 10),
+ ('ROUTER_DEBUG_CHANNEL_ID', 'R_ROUTER_DEBUG_CHANNEL_ID', 10),
+ ('TELEGRAM_API_BASE_URL', 'R_TELEGRAM_API_BASE_URL', 10),
+ ('GITHUB_MCP_SERVER_URL', 'R_GITHUB_MCP_SERVER_URL', 10),
+ ('GITHUB_AUTOMATED_SKIP_REPOS', 'R_GITHUB_AUTOMATED_SKIP_REPOS', 10),
+ ('GITHUB_AUTOMATED_SKIP_OWNERS', 'R_GITHUB_AUTOMATED_SKIP_OWNERS', 10),
+ ('GITLAB_BASE_URL', 'R_GITLAB_BASE_URL', 10),
+ ('GITLAB_CLIENT_ID', 'R_GITLAB_CLIENT_ID', 10),
+ ('GITLAB_CLIENT_SECRET', 'R_GITLAB_CLIENT_SECRET', 10),
+ ('GITLAB_WEBHOOK_SIGNING_TOKEN', 'R_GITLAB_WEBHOOK_SIGNING_TOKEN', 10),
+ ('GITLAB_WEBHOOK_SECRET', 'R_GITLAB_WEBHOOK_SECRET', 10),
+ ('GITEA_BASE_URL', 'R_GITEA_BASE_URL', 10),
+ ('GITEA_USERNAME', 'R_GITEA_USERNAME', 10),
+ ('GITEA_CLIENT_ID', 'R_GITEA_CLIENT_ID', 10),
+ ('GITEA_CLIENT_SECRET', 'R_GITEA_CLIENT_SECRET', 10),
+ ('GITEA_WEBHOOK_SECRET', 'R_GITEA_WEBHOOK_SECRET', 10),
+ ('ADO_ORGANIZATION', 'R_ADO_ORGANIZATION', 10),
+ ('ADO_BASE_URL', 'R_ADO_BASE_URL', 10),
+ ('ADO_USERNAME', 'R_ADO_USERNAME', 10),
+ ('ADO_CLIENT_ID', 'R_ADO_CLIENT_ID', 10),
+ ('ADO_CLIENT_SECRET', 'R_ADO_CLIENT_SECRET', 10),
+ ('ADO_TENANT_ID', 'R_ADO_TENANT_ID', 10),
+ ('ADO_WEBHOOK_SECRET', 'R_ADO_WEBHOOK_SECRET', 10),
+ ('BITBUCKET_USERNAME', 'R_BITBUCKET_USERNAME', 10),
+ ('BITBUCKET_BASE_URL', 'R_BITBUCKET_BASE_URL', 10),
+ ('BITBUCKET_CLIENT_ID', 'R_BITBUCKET_CLIENT_ID', 10),
+ ('BITBUCKET_CLIENT_SECRET', 'R_BITBUCKET_CLIENT_SECRET', 10),
+ ('BITBUCKET_WEBHOOK_SECRET', 'R_BITBUCKET_WEBHOOK_SECRET', 10),
+ ('API_DEBUG_LOGS', 'R_API_DEBUG_LOGS', 10),
+ ('WEBHOOK_RETENTION_DAYS', 'R_WEBHOOK_RETENTION_DAYS', 10),
+ ('API_EXTERNAL_REQUEST_TIMEOUT_MS', 'R_API_EXTERNAL_REQUEST_TIMEOUT_MS', 10),
+ ('API_SLOW_REQUEST_THRESHOLD_MS', 'R_API_SLOW_REQUEST_THRESHOLD_MS', 10),
+ ('API_SLOW_EXTERNAL_REQUEST_THRESHOLD_MS', 'R_API_SLOW_EXTERNAL_REQUEST_THRESHOLD_MS', 10),
+ ('WORKER_RELEASE_CHANNEL', 'R_WORKER_RELEASE_CHANNEL', 10),
+ ('WORKER_RELEASE_VERSION', 'R_WORKER_RELEASE_VERSION', 10)
+),
+ranked_legacy AS (
+ SELECT
+ env_var."id",
+ row_number() OVER (
+ PARTITION BY rename.new_name
+ ORDER BY rename.priority ASC, env_var."updated_at" DESC, env_var."created_at" DESC, env_var."id" ASC
+ ) AS duplicate_rank
+ FROM "environment_variables" env_var
+ JOIN env_var_renames rename
+ ON env_var."name" = rename.old_name
+)
+DELETE FROM "environment_variables" duplicate
+USING ranked_legacy ranked
+WHERE duplicate."id" = ranked."id"
+ AND ranked.duplicate_rank > 1;
+
+WITH env_var_renames(old_name, new_name) AS (
+ VALUES
+ ('DEFAULT_COMPUTE_PROVIDER', 'R_DEFAULT_COMPUTE_PROVIDER'),
+ ('EXCLUDED_COMPUTE_PROVIDERS', 'R_EXCLUDED_COMPUTE_PROVIDERS'),
+ ('DOCKER_WORKER_IMAGE', 'R_DOCKER_WORKER_IMAGE'),
+ ('DOCKER_WORKER_PLATFORM', 'R_DOCKER_WORKER_PLATFORM'),
+ ('DOCKER_WORKER_NETWORK', 'R_DOCKER_WORKER_NETWORK'),
+ ('DOCKER_WORKER_RELEASE_PATH', 'R_DOCKER_WORKER_RELEASE_PATH'),
+ ('DOCKER_WORKER_CPU_LIMIT', 'R_DOCKER_WORKER_CPU_LIMIT'),
+ ('DOCKER_WORKER_MEMORY_LIMIT', 'R_DOCKER_WORKER_MEMORY_LIMIT'),
+ ('DOCKER_WORKER_PIDS_LIMIT', 'R_DOCKER_WORKER_PIDS_LIMIT'),
+ ('DOCKER_WORKER_DISK_LIMIT', 'R_DOCKER_WORKER_DISK_LIMIT'),
+ ('DOCKER_WORKER_ALLOW_UNBOUNDED_DISK', 'R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK'),
+ ('DOCKER_WORKER_LOG_MAX_SIZE', 'R_DOCKER_WORKER_LOG_MAX_SIZE'),
+ ('DOCKER_WORKER_LOG_MAX_FILES', 'R_DOCKER_WORKER_LOG_MAX_FILES'),
+ ('DOCKER_WORKER_EGRESS_POLICY', 'R_DOCKER_WORKER_EGRESS_POLICY'),
+ ('DOCKER_STANDBY_MAX_COUNT', 'R_DOCKER_STANDBY_MAX_COUNT'),
+ ('DOCKER_STANDBY_MAX_AGE_HOURS', 'R_DOCKER_STANDBY_MAX_AGE_HOURS'),
+ ('BLAXEL_STANDBY_MAX_COUNT', 'R_BLAXEL_STANDBY_MAX_COUNT'),
+ ('BLAXEL_STANDBY_MAX_AGE_HOURS', 'R_BLAXEL_STANDBY_MAX_AGE_HOURS'),
+ ('TRPC_URL', 'R_TRPC_URL'),
+ ('PREVIEW_PROXY_BASE_URL', 'R_PREVIEW_PROXY_BASE_URL'),
+ ('PREVIEW_DOMAINS', 'R_PREVIEW_DOMAINS'),
+ ('PREVIEW_TOKEN_TTL_SECONDS', 'R_PREVIEW_TOKEN_TTL_SECONDS'),
+ ('SLACK_APP_ID', 'R_SLACK_APP_ID'),
+ ('SLACK_REDIRECT_URI', 'R_SLACK_REDIRECT_URI'),
+ ('SLACK_AUTH_URI', 'R_SLACK_AUTH_URI'),
+ ('SLACK_API_BASE_URL', 'R_SLACK_API_BASE_URL'),
+ ('SLACK_UNFURL_ALLOWED_DOMAINS', 'R_SLACK_UNFURL_ALLOWED_DOMAINS'),
+ ('SLACK_API_TIMEOUT_MS', 'R_SLACK_API_TIMEOUT_MS'),
+ ('SLACK_DEBUG_LOGS', 'R_SLACK_DEBUG_LOGS'),
+ ('ROUTER_DEBUG_CHANNEL_ID', 'R_ROUTER_DEBUG_CHANNEL_ID'),
+ ('TELEGRAM_API_BASE_URL', 'R_TELEGRAM_API_BASE_URL'),
+ ('GITHUB_MCP_SERVER_URL', 'R_GITHUB_MCP_SERVER_URL'),
+ ('GITHUB_AUTOMATED_SKIP_REPOS', 'R_GITHUB_AUTOMATED_SKIP_REPOS'),
+ ('GITHUB_AUTOMATED_SKIP_OWNERS', 'R_GITHUB_AUTOMATED_SKIP_OWNERS'),
+ ('GITLAB_BASE_URL', 'R_GITLAB_BASE_URL'),
+ ('GITLAB_CLIENT_ID', 'R_GITLAB_CLIENT_ID'),
+ ('GITLAB_CLIENT_SECRET', 'R_GITLAB_CLIENT_SECRET'),
+ ('GITLAB_WEBHOOK_SIGNING_TOKEN', 'R_GITLAB_WEBHOOK_SIGNING_TOKEN'),
+ ('GITLAB_WEBHOOK_SECRET', 'R_GITLAB_WEBHOOK_SECRET'),
+ ('GITEA_BASE_URL', 'R_GITEA_BASE_URL'),
+ ('GITEA_USERNAME', 'R_GITEA_USERNAME'),
+ ('GITEA_CLIENT_ID', 'R_GITEA_CLIENT_ID'),
+ ('GITEA_CLIENT_SECRET', 'R_GITEA_CLIENT_SECRET'),
+ ('GITEA_WEBHOOK_SECRET', 'R_GITEA_WEBHOOK_SECRET'),
+ ('ADO_ORGANIZATION', 'R_ADO_ORGANIZATION'),
+ ('ADO_BASE_URL', 'R_ADO_BASE_URL'),
+ ('ADO_USERNAME', 'R_ADO_USERNAME'),
+ ('ADO_CLIENT_ID', 'R_ADO_CLIENT_ID'),
+ ('ADO_CLIENT_SECRET', 'R_ADO_CLIENT_SECRET'),
+ ('ADO_TENANT_ID', 'R_ADO_TENANT_ID'),
+ ('ADO_WEBHOOK_SECRET', 'R_ADO_WEBHOOK_SECRET'),
+ ('BITBUCKET_USERNAME', 'R_BITBUCKET_USERNAME'),
+ ('BITBUCKET_BASE_URL', 'R_BITBUCKET_BASE_URL'),
+ ('BITBUCKET_CLIENT_ID', 'R_BITBUCKET_CLIENT_ID'),
+ ('BITBUCKET_CLIENT_SECRET', 'R_BITBUCKET_CLIENT_SECRET'),
+ ('BITBUCKET_WEBHOOK_SECRET', 'R_BITBUCKET_WEBHOOK_SECRET'),
+ ('API_DEBUG_LOGS', 'R_API_DEBUG_LOGS'),
+ ('WEBHOOK_RETENTION_DAYS', 'R_WEBHOOK_RETENTION_DAYS'),
+ ('API_EXTERNAL_REQUEST_TIMEOUT_MS', 'R_API_EXTERNAL_REQUEST_TIMEOUT_MS'),
+ ('API_SLOW_REQUEST_THRESHOLD_MS', 'R_API_SLOW_REQUEST_THRESHOLD_MS'),
+ ('API_SLOW_EXTERNAL_REQUEST_THRESHOLD_MS', 'R_API_SLOW_EXTERNAL_REQUEST_THRESHOLD_MS'),
+ ('WORKER_RELEASE_CHANNEL', 'R_WORKER_RELEASE_CHANNEL'),
+ ('WORKER_RELEASE_VERSION', 'R_WORKER_RELEASE_VERSION')
+)
+UPDATE "environment_variables" env_var
+SET "name" = rename.new_name,
+ "updated_at" = now()
+FROM env_var_renames rename
+WHERE env_var."name" = rename.old_name;
diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json
index 52376ce9..95266132 100644
--- a/packages/db/drizzle/meta/_journal.json
+++ b/packages/db/drizzle/meta/_journal.json
@@ -71,6 +71,13 @@
"when": 1783820516382,
"tag": "0009_abnormal_nuke",
"breakpoints": true
+ },
+ {
+ "idx": 10,
+ "version": "7",
+ "when": 1783935000000,
+ "tag": "0010_more_canonical_r_env_vars",
+ "breakpoints": true
}
]
}
diff --git a/packages/db/src/lib/__tests__/compute-runtime-config.test.ts b/packages/db/src/lib/__tests__/compute-runtime-config.test.ts
index d12e74f1..605ce56c 100644
--- a/packages/db/src/lib/__tests__/compute-runtime-config.test.ts
+++ b/packages/db/src/lib/__tests__/compute-runtime-config.test.ts
@@ -79,7 +79,7 @@ describe('resolveComputeProviderEnvValues', () => {
runtimeEnv: {
MODAL_TOKEN_ID: 'id',
MODAL_TOKEN_SECRET: 'secret',
- DOCKER_WORKER_IMAGE: 'ghcr.io/roocodeinc/roomote-worker:v1.2.3',
+ R_DOCKER_WORKER_IMAGE: 'ghcr.io/roocodeinc/roomote-worker:v1.2.3',
},
executor: makeExecutor([]),
});
@@ -113,7 +113,7 @@ describe('resolveComputeProviderEnvValues', () => {
},
executor: makeExecutor([
{
- name: 'DOCKER_WORKER_IMAGE',
+ name: 'R_DOCKER_WORKER_IMAGE',
value: 'ghcr.io/roocodeinc/roomote-worker:v9.9.9',
},
]),
@@ -130,7 +130,7 @@ describe('resolveComputeProviderEnvValues', () => {
NODE_ENV: 'development',
MODAL_TOKEN_ID: 'id',
MODAL_TOKEN_SECRET: 'secret',
- DOCKER_WORKER_IMAGE: 'roomote-worker:local',
+ R_DOCKER_WORKER_IMAGE: 'roomote-worker:local',
},
executor: makeExecutor([]),
});
@@ -145,7 +145,7 @@ describe('resolveComputeProviderEnvValues', () => {
runtimeEnv: {
MODAL_TOKEN_ID: 'id',
MODAL_TOKEN_SECRET: 'secret',
- DOCKER_WORKER_IMAGE: 'roomote-worker:local',
+ R_DOCKER_WORKER_IMAGE: 'roomote-worker:local',
RELEASE_VERSION: 'self-host-production',
},
executor: makeExecutor([]),
@@ -156,15 +156,15 @@ describe('resolveComputeProviderEnvValues', () => {
it('normalizes typed Docker standby settings with runtime values taking precedence', async () => {
const values = await resolveComputeProviderEnvValues('docker', {
- runtimeEnv: { DOCKER_STANDBY_MAX_COUNT: 7 },
+ runtimeEnv: { R_DOCKER_STANDBY_MAX_COUNT: 7 },
executor: makeExecutor([
- { name: 'DOCKER_STANDBY_MAX_AGE_HOURS', value: '18' },
+ { name: 'R_DOCKER_STANDBY_MAX_AGE_HOURS', value: '18' },
]),
});
expect(values).toEqual({
- DOCKER_STANDBY_MAX_COUNT: '7',
- DOCKER_STANDBY_MAX_AGE_HOURS: '18',
+ R_DOCKER_STANDBY_MAX_COUNT: '7',
+ R_DOCKER_STANDBY_MAX_AGE_HOURS: '18',
});
});
});
@@ -181,7 +181,7 @@ describe('listConfiguredComputeProviders', () => {
MODAL_TOKEN_ID: 'id',
MODAL_TOKEN_SECRET: 'secret',
MODAL_BASE_IMAGE_REF: 'registry.example.com/image:tag',
- EXCLUDED_COMPUTE_PROVIDERS: 'docker',
+ R_EXCLUDED_COMPUTE_PROVIDERS: 'docker',
},
executor: makeExecutor([]),
});
diff --git a/packages/db/src/lib/compute-runtime-config.ts b/packages/db/src/lib/compute-runtime-config.ts
index d8b66866..066c05dc 100644
--- a/packages/db/src/lib/compute-runtime-config.ts
+++ b/packages/db/src/lib/compute-runtime-config.ts
@@ -83,7 +83,7 @@ export async function listConfiguredComputeProviders(
const runtimeEnv = options.runtimeEnv ?? process.env;
const executor = options.executor ?? db;
const excludedProviders = parseExcludedComputeProviders(
- runtimeEnv.EXCLUDED_COMPUTE_PROVIDERS,
+ runtimeEnv.R_EXCLUDED_COMPUTE_PROVIDERS,
);
// Preserve setup-catalog display order so callers that fall back to the first
@@ -110,7 +110,7 @@ export async function listConfiguredComputeProviders(
/**
* Resolves the deployment default compute provider. The persisted setup
- * choice wins over the DEFAULT_COMPUTE_PROVIDER env value because compose and
+ * choice wins over the R_DEFAULT_COMPUTE_PROVIDER env value because compose and
* PM2 stacks always inject an env default; the admin's explicit setup choice
* is the stronger signal.
*/
@@ -129,9 +129,9 @@ export async function resolveDefaultComputeProvider(
return persistedComputeConfig.defaultProvider;
}
- const runtimeDefault = runtimeEnv.DEFAULT_COMPUTE_PROVIDER?.trim();
+ const runtimeDefault = runtimeEnv.R_DEFAULT_COMPUTE_PROVIDER?.trim();
const excludedProviders = parseExcludedComputeProviders(
- runtimeEnv.EXCLUDED_COMPUTE_PROVIDERS,
+ runtimeEnv.R_EXCLUDED_COMPUTE_PROVIDERS,
);
if (
@@ -167,7 +167,7 @@ export async function resolveDefaultComputeProvider(
* the process env and falling back to encrypted deployment environment
* variables saved during setup. For Modal, a still-missing base image ref
* falls back to the deployment's effective worker image (the explicit
- * DOCKER_WORKER_IMAGE or the ref derived from the baked RELEASE_VERSION),
+ * R_DOCKER_WORKER_IMAGE or the ref derived from the baked RELEASE_VERSION),
* then the development-only GHCR latest image. The published worker image
* doubles as the Modal base image.
*/
@@ -231,19 +231,19 @@ export async function resolveComputeProviderEnvValues(
}
// Effective worker image is deploy/runtime-managed only: process env
- // DOCKER_WORKER_IMAGE, then the ref derived from the baked RELEASE_VERSION.
+ // R_DOCKER_WORKER_IMAGE, then the ref derived from the baked RELEASE_VERSION.
// Legacy deployment-env rows from the removed Settings worker-image UI are
// intentionally ignored so a sticky saved value cannot pin release-derived
// workers back to a stale image. Development falls back to the public
// latest image when no hosted image is derivable.
if (provider === 'modal' && !resolvedValues.MODAL_BASE_IMAGE_REF) {
const effectiveWorkerImage =
- runtimeEnv.DOCKER_WORKER_IMAGE?.trim() ||
+ runtimeEnv.R_DOCKER_WORKER_IMAGE?.trim() ||
deriveWorkerImageFromReleaseVersion(runtimeEnv) ||
undefined;
const derivedBaseImageRef = resolveDerivedModalBaseImageRef({
...runtimeEnv,
- DOCKER_WORKER_IMAGE: effectiveWorkerImage,
+ R_DOCKER_WORKER_IMAGE: effectiveWorkerImage,
});
if (derivedBaseImageRef) {
@@ -266,7 +266,7 @@ export async function resolveSavedWorkerImage(
}
/**
- * Deletes any deployment-scoped `DOCKER_WORKER_IMAGE` rows left from the
+ * Deletes any deployment-scoped `R_DOCKER_WORKER_IMAGE` rows left from the
* removed Settings worker-image editor so they cannot linger as reserved,
* hidden, sticky configuration.
*/
diff --git a/packages/db/src/lib/preview-runtime-config.ts b/packages/db/src/lib/preview-runtime-config.ts
index 14558a3d..9f12ba16 100644
--- a/packages/db/src/lib/preview-runtime-config.ts
+++ b/packages/db/src/lib/preview-runtime-config.ts
@@ -52,7 +52,7 @@ function readPersistedFields(
deploymentEnvVars: Record,
): PreviewRuntimeConfigFields {
const previewProxyBaseUrl = normalizeConfiguredValue(
- deploymentEnvVars.PREVIEW_PROXY_BASE_URL,
+ deploymentEnvVars.R_PREVIEW_PROXY_BASE_URL,
);
return {
@@ -102,7 +102,7 @@ function buildOverrideState(params: {
const overriddenFields: PreviewRuntimeOverrideField[] = [];
const runtimeBaseUrl = normalizeConfiguredValue(
- params.runtimeEnv.PREVIEW_PROXY_BASE_URL,
+ params.runtimeEnv.R_PREVIEW_PROXY_BASE_URL,
);
if (
runtimeBaseUrl &&
@@ -114,7 +114,7 @@ function buildOverrideState(params: {
}
const runtimeDomains = normalizeConfiguredValue(
- params.runtimeEnv.PREVIEW_DOMAINS,
+ params.runtimeEnv.R_PREVIEW_DOMAINS,
);
if (
runtimeDomains &&
@@ -160,19 +160,19 @@ export async function resolveEffectivePreviewRuntimeConfig(
(await resolveEffectiveDeploymentEnvVars({ executor }));
const persisted = readPersistedFields(deploymentEnvVars);
const effectivePreviewProxyBaseUrl = resolveEffectiveValue({
- runtimeValue: runtimeEnv.PREVIEW_PROXY_BASE_URL,
+ runtimeValue: runtimeEnv.R_PREVIEW_PROXY_BASE_URL,
persistedValue: persisted.previewProxyBaseUrl,
defaultValue: options.defaultPreviewProxyBaseUrl,
});
const previewProxyBaseUrlSource = resolvePreviewProxyBaseUrlSource({
- runtimeValue: runtimeEnv.PREVIEW_PROXY_BASE_URL,
+ runtimeValue: runtimeEnv.R_PREVIEW_PROXY_BASE_URL,
persistedValue: persisted.previewProxyBaseUrl,
defaultValue: options.defaultPreviewProxyBaseUrl,
});
const effective: PreviewRuntimeConfigFields = {
previewProxyBaseUrl: effectivePreviewProxyBaseUrl,
previewDomains:
- normalizeConfiguredValue(runtimeEnv.PREVIEW_DOMAINS) ??
+ normalizeConfiguredValue(runtimeEnv.R_PREVIEW_DOMAINS) ??
derivePreviewDomains(effectivePreviewProxyBaseUrl) ??
normalizeConfiguredValue(options.defaultPreviewDomains),
roomotePreviewDomain:
diff --git a/packages/db/src/lib/router-debug-settings.test.ts b/packages/db/src/lib/router-debug-settings.test.ts
index 7c7f6cda..e92855f7 100644
--- a/packages/db/src/lib/router-debug-settings.test.ts
+++ b/packages/db/src/lib/router-debug-settings.test.ts
@@ -47,7 +47,7 @@ describe('router debug settings', () => {
await expect(
getRouterDebugSettings({
runtimeEnv: {
- ROUTER_DEBUG_CHANNEL_ID: 'CENV123456',
+ R_ROUTER_DEBUG_CHANNEL_ID: 'CENV123456',
},
}),
).resolves.toMatchObject({
@@ -66,7 +66,7 @@ describe('router debug settings', () => {
await expect(
getConfiguredRouterDebugSlackChannelId({
runtimeEnv: {
- ROUTER_DEBUG_CHANNEL_ID: ' cenv123456 ',
+ R_ROUTER_DEBUG_CHANNEL_ID: ' cenv123456 ',
},
}),
).resolves.toBe('CENV123456');
diff --git a/packages/db/src/lib/router-debug-settings.ts b/packages/db/src/lib/router-debug-settings.ts
index 79d2598b..60b0ed6a 100644
--- a/packages/db/src/lib/router-debug-settings.ts
+++ b/packages/db/src/lib/router-debug-settings.ts
@@ -23,7 +23,7 @@ export function normalizeRouterDebugSlackChannelId(
export async function getRouterDebugSettings(
options: {
executor?: DatabaseOrTransaction;
- runtimeEnv?: Pick;
+ runtimeEnv?: Pick;
} = {},
): Promise<{
routerDebugSlackChannelId: string | null;
@@ -43,7 +43,7 @@ export async function getRouterDebugSettings(
deployment?.routerDebugSlackChannelId,
);
const envFallbackSlackChannelId = normalizeRouterDebugSlackChannelId(
- (options.runtimeEnv ?? process.env).ROUTER_DEBUG_CHANNEL_ID,
+ (options.runtimeEnv ?? process.env).R_ROUTER_DEBUG_CHANNEL_ID,
);
const effectiveRouterDebugSlackChannelId =
routerDebugSlackChannelId ?? envFallbackSlackChannelId;
@@ -63,7 +63,7 @@ export async function getRouterDebugSettings(
export async function getConfiguredRouterDebugSlackChannelId(
options: {
executor?: DatabaseOrTransaction;
- runtimeEnv?: Pick;
+ runtimeEnv?: Pick;
} = {},
): Promise {
const settings = await getRouterDebugSettings(options);
diff --git a/packages/db/src/lib/telegram-runtime-credentials.test.ts b/packages/db/src/lib/telegram-runtime-credentials.test.ts
index 37ddb17a..08df1bb7 100644
--- a/packages/db/src/lib/telegram-runtime-credentials.test.ts
+++ b/packages/db/src/lib/telegram-runtime-credentials.test.ts
@@ -37,7 +37,7 @@ describe('resolveTelegramRuntimeCredentials', () => {
invalidateTelegramRuntimeCredentialsCache();
process.env.R_TELEGRAM_BOT_TOKEN = '123:token';
process.env.R_TELEGRAM_WEBHOOK_SECRET = 'secret';
- process.env.TELEGRAM_API_BASE_URL = 'https://telegram.example.test';
+ process.env.R_TELEGRAM_API_BASE_URL = 'https://telegram.example.test';
resolveEffectiveDeploymentEnvVarsMock.mockResolvedValue({});
deploymentSettingsFindFirstMock.mockResolvedValue({ metadata: {} });
deploymentSettingsUpdateSetMock.mockReturnValue({
diff --git a/packages/db/src/lib/telegram-runtime-credentials.ts b/packages/db/src/lib/telegram-runtime-credentials.ts
index f5559319..a8696707 100644
--- a/packages/db/src/lib/telegram-runtime-credentials.ts
+++ b/packages/db/src/lib/telegram-runtime-credentials.ts
@@ -70,7 +70,7 @@ async function resolveTelegramBotUsername(
try {
const apiBaseUrl =
- process.env.TELEGRAM_API_BASE_URL?.replace(/\/$/u, '') ??
+ process.env.R_TELEGRAM_API_BASE_URL?.replace(/\/$/u, '') ??
'https://api.telegram.org';
const response = await fetch(`${apiBaseUrl}/bot${botToken}/getMe`, {
method: 'POST',
diff --git a/packages/db/src/lib/webhook-retention.ts b/packages/db/src/lib/webhook-retention.ts
index b4846add..2e25baee 100644
--- a/packages/db/src/lib/webhook-retention.ts
+++ b/packages/db/src/lib/webhook-retention.ts
@@ -20,7 +20,7 @@ export interface DeleteExpiredWebhooksOptions {
/**
* Deletes recorded webhook rows older than the cutoff, in batches, and
* returns the number of rows removed. Used by the WebhookCleanup scheduled
- * job (apps/bullmq) with a cutoff derived from WEBHOOK_RETENTION_DAYS.
+ * job (apps/bullmq) with a cutoff derived from R_WEBHOOK_RETENTION_DAYS.
*/
export async function deleteExpiredWebhooks(
database: DatabaseOrTransaction,
diff --git a/packages/env/src/__tests__/index.test.ts b/packages/env/src/__tests__/index.test.ts
index 637c4749..bed06360 100644
--- a/packages/env/src/__tests__/index.test.ts
+++ b/packages/env/src/__tests__/index.test.ts
@@ -24,7 +24,7 @@ const productionCoreEnv: NodeJS.ProcessEnv = {
NODE_ENV: 'production',
R_APP_ENV: 'production',
R_APP_URL: 'https://roomote.example.com',
- TRPC_URL: 'https://api.roomote.example.com',
+ R_TRPC_URL: 'https://api.roomote.example.com',
DATABASE_URL: 'postgres://postgres:password@postgres:5432/roomote',
REDIS_URL: 'redis://redis:6379',
S3_ENDPOINT: 'https://s3.example.com',
@@ -40,8 +40,8 @@ const productionCoreEnv: NodeJS.ProcessEnv = {
DASHBOARD_PASSWORD: 'roomote-admin-password',
ENCRYPTION_KEY: '12345678901234567890123456789012',
ARTIFACT_SIGNING_KEY: '12345678901234567890123456789012',
- PREVIEW_PROXY_BASE_URL: 'https://preview.roomote.example.com',
- PREVIEW_DOMAINS: 'preview.roomote.example.com',
+ R_PREVIEW_PROXY_BASE_URL: 'https://preview.roomote.example.com',
+ R_PREVIEW_DOMAINS: 'preview.roomote.example.com',
R_GITHUB_APP_SLUG: 'roomote-dev',
};
@@ -54,11 +54,11 @@ describe('Env', () => {
expect(databaseUrl.pathname).toContain('test');
for (const maybeNumber of [
- Env.PREVIEW_TOKEN_TTL_SECONDS,
- Env.SLACK_API_TIMEOUT_MS,
- Env.API_EXTERNAL_REQUEST_TIMEOUT_MS,
- Env.API_SLOW_REQUEST_THRESHOLD_MS,
- Env.API_SLOW_EXTERNAL_REQUEST_THRESHOLD_MS,
+ Env.R_PREVIEW_TOKEN_TTL_SECONDS,
+ Env.R_SLACK_API_TIMEOUT_MS,
+ Env.R_API_EXTERNAL_REQUEST_TIMEOUT_MS,
+ Env.R_API_SLOW_REQUEST_THRESHOLD_MS,
+ Env.R_API_SLOW_EXTERNAL_REQUEST_THRESHOLD_MS,
]) {
if (typeof maybeNumber === 'undefined') {
continue;
@@ -78,34 +78,54 @@ describe('Env', () => {
delete runtimeEnv.SKIP_ENV_VALIDATION;
delete runtimeEnv.ARTIFACT_SIGNING_KEY_PREVIOUS;
delete runtimeEnv.SANDBOX_OIDC_PUBLIC_KEY_SECONDARY;
- delete runtimeEnv.PREVIEW_TOKEN_TTL_SECONDS;
+ delete runtimeEnv.R_PREVIEW_TOKEN_TTL_SECONDS;
delete runtimeEnv.SKIP_ENV_VALIDATION;
+ // Clear model overrides so host/task runtime env cannot leak into the
+ // defaulting assertions below.
+ delete runtimeEnv.R_MODEL;
+ delete runtimeEnv.R_SMALL_MODEL;
+ delete runtimeEnv.R_VISION_MODEL;
+ delete runtimeEnv.R_CODE_REVIEW_MODEL;
+ delete runtimeEnv.R_EXPLORE_MODEL;
+ delete runtimeEnv.R_PLANNING_MODEL;
+ delete runtimeEnv.R_MODEL_REASONING_EFFORT;
+ delete runtimeEnv.R_SMALL_MODEL_REASONING_EFFORT;
+ delete runtimeEnv.R_VISION_MODEL_REASONING_EFFORT;
+ delete runtimeEnv.R_CODE_REVIEW_MODEL_REASONING_EFFORT;
+ delete runtimeEnv.R_EXPLORE_MODEL_REASONING_EFFORT;
+ delete runtimeEnv.R_PLANNING_MODEL_REASONING_EFFORT;
+ delete runtimeEnv.R_MODEL_ENV_KEYS;
+ delete runtimeEnv.R_EXCLUDED_COMPUTE_PROVIDERS;
+ delete runtimeEnv.R_DOCKER_WORKER_IMAGE;
+ delete runtimeEnv.R_DOCKER_WORKER_NETWORK;
+ delete runtimeEnv.R_DOCKER_WORKER_RELEASE_PATH;
+ delete runtimeEnv.R_DEFAULT_COMPUTE_PROVIDER;
try {
const env = createRoomoteEnv(runtimeEnv);
expect(env.ARTIFACT_SIGNING_KEY_PREVIOUS).toBeUndefined();
expect(env.SANDBOX_OIDC_PUBLIC_KEY_SECONDARY).toBeUndefined();
- expect(env.DEFAULT_COMPUTE_PROVIDER).toBe('docker');
- expect(env.EXCLUDED_COMPUTE_PROVIDERS).toBeUndefined();
- expect(env.DOCKER_WORKER_IMAGE).toBe('roomote-worker:local');
- expect(env.DOCKER_WORKER_PLATFORM).toBe(
+ expect(env.R_DEFAULT_COMPUTE_PROVIDER).toBe('docker');
+ expect(env.R_EXCLUDED_COMPUTE_PROVIDERS).toBeUndefined();
+ expect(env.R_DOCKER_WORKER_IMAGE).toBe('roomote-worker:local');
+ expect(env.R_DOCKER_WORKER_PLATFORM).toBe(
process.arch === 'arm64' ? 'linux/arm64' : 'linux/amd64',
);
- expect(env.DOCKER_WORKER_NETWORK).toBeUndefined();
- expect(env.DOCKER_WORKER_RELEASE_PATH).toBeUndefined();
- expect(env.DOCKER_WORKER_CPU_LIMIT).toBe(2);
- expect(env.DOCKER_WORKER_MEMORY_LIMIT).toBe('4g');
- expect(env.DOCKER_WORKER_PIDS_LIMIT).toBe(512);
- expect(env.DOCKER_WORKER_DISK_LIMIT).toBe('20g');
- expect(env.DOCKER_WORKER_ALLOW_UNBOUNDED_DISK).toBe(false);
- expect(env.DOCKER_WORKER_LOG_MAX_SIZE).toBe('10m');
- expect(env.DOCKER_WORKER_LOG_MAX_FILES).toBe(3);
- expect(env.DOCKER_WORKER_EGRESS_POLICY).toBe('internet');
- expect(env.DOCKER_STANDBY_MAX_COUNT).toBe(10);
- expect(env.DOCKER_STANDBY_MAX_AGE_HOURS).toBe(24);
- expect(env.BLAXEL_STANDBY_MAX_COUNT).toBe(25);
- expect(env.BLAXEL_STANDBY_MAX_AGE_HOURS).toBe(168);
+ expect(env.R_DOCKER_WORKER_NETWORK).toBeUndefined();
+ expect(env.R_DOCKER_WORKER_RELEASE_PATH).toBeUndefined();
+ expect(env.R_DOCKER_WORKER_CPU_LIMIT).toBe(2);
+ expect(env.R_DOCKER_WORKER_MEMORY_LIMIT).toBe('4g');
+ expect(env.R_DOCKER_WORKER_PIDS_LIMIT).toBe(512);
+ expect(env.R_DOCKER_WORKER_DISK_LIMIT).toBe('20g');
+ expect(env.R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK).toBe(false);
+ expect(env.R_DOCKER_WORKER_LOG_MAX_SIZE).toBe('10m');
+ expect(env.R_DOCKER_WORKER_LOG_MAX_FILES).toBe(3);
+ expect(env.R_DOCKER_WORKER_EGRESS_POLICY).toBe('internet');
+ expect(env.R_DOCKER_STANDBY_MAX_COUNT).toBe(10);
+ expect(env.R_DOCKER_STANDBY_MAX_AGE_HOURS).toBe(24);
+ expect(env.R_BLAXEL_STANDBY_MAX_COUNT).toBe(25);
+ expect(env.R_BLAXEL_STANDBY_MAX_AGE_HOURS).toBe(168);
expect(env.R_MODEL).toBeUndefined();
expect(env.R_SMALL_MODEL).toBeUndefined();
expect(env.R_VISION_MODEL).toBeUndefined();
@@ -125,7 +145,7 @@ describe('Env', () => {
expect(env.S3_ACCESS_KEY_ID).toBe('roomote');
expect(env.S3_SECRET_ACCESS_KEY).toBe('roomote-local-artifacts-password');
expect(env.S3_BUCKET_ARTIFACTS).toBe('roomote-artifacts');
- expect(env.PREVIEW_TOKEN_TTL_SECONDS).toBe(3600);
+ expect(env.R_PREVIEW_TOKEN_TTL_SECONDS).toBe(3600);
} finally {
if (previousSkipEnvValidation === undefined) {
delete process.env.SKIP_ENV_VALIDATION;
@@ -138,35 +158,35 @@ describe('Env', () => {
it('requires an explicit opt-in for unbounded Docker task disks', () => {
const runtimeEnv: NodeJS.ProcessEnv = {
...process.env,
- DOCKER_WORKER_ALLOW_UNBOUNDED_DISK: 'true',
+ R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK: 'true',
};
delete runtimeEnv.SKIP_ENV_VALIDATION;
expect(
- createRoomoteEnv(runtimeEnv).DOCKER_WORKER_ALLOW_UNBOUNDED_DISK,
+ createRoomoteEnv(runtimeEnv).R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK,
).toBe(true);
});
- it('derives DOCKER_WORKER_IMAGE from the baked release version', () => {
+ it('derives R_DOCKER_WORKER_IMAGE from the baked release version', () => {
const env = createRoomoteEnv({
...process.env,
- DOCKER_WORKER_IMAGE: undefined,
+ R_DOCKER_WORKER_IMAGE: undefined,
RELEASE_VERSION: 'v1.2.3',
});
- expect(env.DOCKER_WORKER_IMAGE).toBe(
+ expect(env.R_DOCKER_WORKER_IMAGE).toBe(
'ghcr.io/roocodeinc/roomote-worker:v1.2.3',
);
});
- it('treats an empty DOCKER_WORKER_IMAGE as unset for the derived default', () => {
+ it('treats an empty R_DOCKER_WORKER_IMAGE as unset for the derived default', () => {
const env = createRoomoteEnv({
...process.env,
- DOCKER_WORKER_IMAGE: '',
+ R_DOCKER_WORKER_IMAGE: '',
RELEASE_VERSION: 'v1.2.3',
});
- expect(env.DOCKER_WORKER_IMAGE).toBe(
+ expect(env.R_DOCKER_WORKER_IMAGE).toBe(
'ghcr.io/roocodeinc/roomote-worker:v1.2.3',
);
});
@@ -174,24 +194,24 @@ describe('Env', () => {
it('honors a ROOMOTE_WORKER_IMAGE_REPO override for the derived worker image', () => {
const env = createRoomoteEnv({
...process.env,
- DOCKER_WORKER_IMAGE: undefined,
+ R_DOCKER_WORKER_IMAGE: undefined,
RELEASE_VERSION: 'v1.2.3',
ROOMOTE_WORKER_IMAGE_REPO: 'registry.example.com/fork/roomote-worker',
});
- expect(env.DOCKER_WORKER_IMAGE).toBe(
+ expect(env.R_DOCKER_WORKER_IMAGE).toBe(
'registry.example.com/fork/roomote-worker:v1.2.3',
);
});
- it('prefers an explicit DOCKER_WORKER_IMAGE over the release-derived default', () => {
+ it('prefers an explicit R_DOCKER_WORKER_IMAGE over the release-derived default', () => {
const env = createRoomoteEnv({
...process.env,
- DOCKER_WORKER_IMAGE: 'registry.example.com/custom/worker:pinned',
+ R_DOCKER_WORKER_IMAGE: 'registry.example.com/custom/worker:pinned',
RELEASE_VERSION: 'v1.2.3',
});
- expect(env.DOCKER_WORKER_IMAGE).toBe(
+ expect(env.R_DOCKER_WORKER_IMAGE).toBe(
'registry.example.com/custom/worker:pinned',
);
});
@@ -206,21 +226,21 @@ describe('Env', () => {
]) {
const env = createRoomoteEnv({
...process.env,
- DOCKER_WORKER_IMAGE: undefined,
+ R_DOCKER_WORKER_IMAGE: undefined,
RELEASE_VERSION: releaseVersion,
});
- expect(env.DOCKER_WORKER_IMAGE).toBe('roomote-worker:local');
+ expect(env.R_DOCKER_WORKER_IMAGE).toBe('roomote-worker:local');
}
});
it('accepts an explicit default compute provider override', () => {
const env = createRoomoteEnv({
...process.env,
- DEFAULT_COMPUTE_PROVIDER: 'modal',
+ R_DEFAULT_COMPUTE_PROVIDER: 'modal',
});
- expect(env.DEFAULT_COMPUTE_PROVIDER).toBe('modal');
+ expect(env.R_DEFAULT_COMPUTE_PROVIDER).toBe('modal');
});
it('accepts explicit model config overrides', () => {
@@ -265,25 +285,25 @@ describe('Env', () => {
delete runtimeEnv.SKIP_ENV_VALIDATION;
delete runtimeEnv.R_APP_URL;
- delete runtimeEnv.TRPC_URL;
+ delete runtimeEnv.R_TRPC_URL;
delete runtimeEnv.DATABASE_URL;
delete runtimeEnv.REDIS_URL;
- delete runtimeEnv.PREVIEW_PROXY_BASE_URL;
- delete runtimeEnv.PREVIEW_DOMAINS;
+ delete runtimeEnv.R_PREVIEW_PROXY_BASE_URL;
+ delete runtimeEnv.R_PREVIEW_DOMAINS;
try {
const env = createRoomoteEnv(runtimeEnv);
expect(env.R_APP_URL).toBe('http://localhost:13000');
- expect(env.TRPC_URL).toBe('http://localhost:13001');
+ expect(env.R_TRPC_URL).toBe('http://localhost:13001');
expect(env.DATABASE_URL).toBe(
'postgres://postgres:password@localhost:15432/roomote_development',
);
expect(env.REDIS_URL).toBe('redis://localhost:16379');
- expect(env.PREVIEW_PROXY_BASE_URL).toBe(
+ expect(env.R_PREVIEW_PROXY_BASE_URL).toBe(
'http://roomotepreview.localhost:18081',
);
- expect(env.PREVIEW_DOMAINS).toBe(
+ expect(env.R_PREVIEW_DOMAINS).toBe(
'localhost,127.0.0.1,roomotepreview.localhost',
);
} finally {
@@ -319,7 +339,7 @@ describe('Env', () => {
APP_ENV: 'production',
ROOMOTE_SERVICE: 'controller',
R_APP_URL: 'https://roomote.example.com',
- TRPC_URL: 'https://api.roomote.example.com',
+ R_TRPC_URL: 'https://api.roomote.example.com',
DATABASE_URL: 'postgres://postgres:password@postgres:5432/roomote',
REDIS_URL: 'redis://redis:6379',
JOB_AUTH_PRIVATE_KEY: 'job-private-key',
@@ -377,14 +397,14 @@ describe('Env', () => {
const runtimeEnv = { ...productionCoreEnv };
delete runtimeEnv.SKIP_ENV_VALIDATION;
- delete runtimeEnv.PREVIEW_PROXY_BASE_URL;
- delete runtimeEnv.PREVIEW_DOMAINS;
+ delete runtimeEnv.R_PREVIEW_PROXY_BASE_URL;
+ delete runtimeEnv.R_PREVIEW_DOMAINS;
try {
const env = createRoomoteEnv(runtimeEnv);
- expect(env.PREVIEW_PROXY_BASE_URL).toBe('');
- expect(env.PREVIEW_DOMAINS).toBe('');
+ expect(env.R_PREVIEW_PROXY_BASE_URL).toBe('');
+ expect(env.R_PREVIEW_DOMAINS).toBe('');
} finally {
if (previousSkipEnvValidation === undefined) {
delete process.env.SKIP_ENV_VALIDATION;
@@ -480,18 +500,18 @@ describe('Env', () => {
delete runtimeEnv.R_GITHUB_CLIENT_ID;
delete runtimeEnv.R_GITHUB_CLIENT_SECRET;
delete runtimeEnv.R_GITHUB_WEBHOOK_SECRET;
- delete runtimeEnv.SLACK_APP_ID;
+ delete runtimeEnv.R_SLACK_APP_ID;
delete runtimeEnv.R_SLACK_CLIENT_ID;
delete runtimeEnv.R_SLACK_CLIENT_SECRET;
- delete runtimeEnv.SLACK_REDIRECT_URI;
- delete runtimeEnv.SLACK_AUTH_URI;
+ delete runtimeEnv.R_SLACK_REDIRECT_URI;
+ delete runtimeEnv.R_SLACK_AUTH_URI;
delete runtimeEnv.R_SLACK_SIGNING_SECRET;
try {
const env = createRoomoteEnv(runtimeEnv);
expect(env.R_GITHUB_APP_ID).toBe('');
- expect(env.SLACK_APP_ID).toBe('');
+ expect(env.R_SLACK_APP_ID).toBe('');
} finally {
if (previousSkipEnvValidation === undefined) {
delete process.env.SKIP_ENV_VALIDATION;
@@ -501,25 +521,25 @@ describe('Env', () => {
}
});
- it('derives SLACK_AUTH_URI from R_APP_URL when unset or empty', () => {
+ it('derives R_SLACK_AUTH_URI from R_APP_URL when unset or empty', () => {
const previousSkipEnvValidation = process.env.SKIP_ENV_VALIDATION;
const unsetEnv = { ...productionCoreEnv };
delete unsetEnv.SKIP_ENV_VALIDATION;
- delete unsetEnv.SLACK_AUTH_URI;
+ delete unsetEnv.R_SLACK_AUTH_URI;
const emptyEnv: NodeJS.ProcessEnv = {
...unsetEnv,
- SLACK_AUTH_URI: '',
+ R_SLACK_AUTH_URI: '',
R_APP_URL: 'https://roomote.example.com/',
};
try {
- expect(createRoomoteEnv(unsetEnv).SLACK_AUTH_URI).toBe(
+ expect(createRoomoteEnv(unsetEnv).R_SLACK_AUTH_URI).toBe(
'https://roomote.example.com/api/slack/auth',
);
// Trailing slashes on R_APP_URL must not produce a double slash.
- expect(createRoomoteEnv(emptyEnv).SLACK_AUTH_URI).toBe(
+ expect(createRoomoteEnv(emptyEnv).R_SLACK_AUTH_URI).toBe(
'https://roomote.example.com/api/slack/auth',
);
} finally {
@@ -531,11 +551,11 @@ describe('Env', () => {
}
});
- it('prefers an explicit SLACK_AUTH_URI over the derived value', () => {
+ it('prefers an explicit R_SLACK_AUTH_URI over the derived value', () => {
const previousSkipEnvValidation = process.env.SKIP_ENV_VALIDATION;
const runtimeEnv: NodeJS.ProcessEnv = {
...productionCoreEnv,
- SLACK_AUTH_URI: 'https://auth.example.com/slack',
+ R_SLACK_AUTH_URI: 'https://auth.example.com/slack',
};
delete runtimeEnv.SKIP_ENV_VALIDATION;
@@ -543,7 +563,7 @@ describe('Env', () => {
try {
const env = createRoomoteEnv(runtimeEnv);
- expect(env.SLACK_AUTH_URI).toBe('https://auth.example.com/slack');
+ expect(env.R_SLACK_AUTH_URI).toBe('https://auth.example.com/slack');
} finally {
if (previousSkipEnvValidation === undefined) {
delete process.env.SKIP_ENV_VALIDATION;
@@ -678,13 +698,13 @@ describe('getDefaultTrpcUrl', () => {
it('requires explicit API configuration for preview', () => {
expect(() => getDefaultTrpcUrl('preview')).toThrow(
- 'TRPC_URL must be configured explicitly for preview',
+ 'R_TRPC_URL must be configured explicitly for preview',
);
});
it('requires explicit API configuration for production', () => {
expect(() => getDefaultTrpcUrl('production')).toThrow(
- 'TRPC_URL must be configured explicitly for production',
+ 'R_TRPC_URL must be configured explicitly for production',
);
});
});
@@ -715,10 +735,10 @@ describe('getDefaultPreviewProxyBaseUrl', () => {
it('requires explicit preview proxy configuration outside development', () => {
expect(() => getDefaultPreviewProxyBaseUrl('preview')).toThrow(
- 'PREVIEW_PROXY_BASE_URL must be configured explicitly for preview',
+ 'R_PREVIEW_PROXY_BASE_URL must be configured explicitly for preview',
);
expect(() => getDefaultPreviewProxyBaseUrl('production')).toThrow(
- 'PREVIEW_PROXY_BASE_URL must be configured explicitly for production',
+ 'R_PREVIEW_PROXY_BASE_URL must be configured explicitly for production',
);
});
});
@@ -775,7 +795,7 @@ describe('getAllowedDevOrigins', () => {
]);
});
- it('adds wildcard preview domains from PREVIEW_DOMAINS', () => {
+ it('adds wildcard preview domains from R_PREVIEW_DOMAINS', () => {
expect(
getAllowedDevOrigins(
'preview.octomote.run, preview-john.ngrok.app, preview.octomote.run',
diff --git a/packages/env/src/app-env.ts b/packages/env/src/app-env.ts
index e5aad7d1..bd612909 100644
--- a/packages/env/src/app-env.ts
+++ b/packages/env/src/app-env.ts
@@ -80,7 +80,7 @@ export function getDefaultTrpcUrl(appEnv: AppEnv): string {
return 'http://localhost:13001';
}
- throw new Error(`TRPC_URL must be configured explicitly for ${appEnv}`);
+ throw new Error(`R_TRPC_URL must be configured explicitly for ${appEnv}`);
}
export function getDefaultDocsUrl(
@@ -96,6 +96,6 @@ export function getDefaultPreviewProxyBaseUrl(appEnv: AppEnv): string {
}
throw new Error(
- `PREVIEW_PROXY_BASE_URL must be configured explicitly for ${appEnv}`,
+ `R_PREVIEW_PROXY_BASE_URL must be configured explicitly for ${appEnv}`,
);
}
diff --git a/packages/env/src/index.ts b/packages/env/src/index.ts
index bad660c5..107dbaaa 100644
--- a/packages/env/src/index.ts
+++ b/packages/env/src/index.ts
@@ -61,11 +61,11 @@ function optInBoolean() {
const serverSchema = {
R_APP_ENV: z.enum(['development', 'preview', 'production']).optional(),
APP_ENV: z.enum(['development', 'preview', 'production']).optional(),
- DEFAULT_COMPUTE_PROVIDER: z
+ R_DEFAULT_COMPUTE_PROVIDER: z
.enum(['modal', 'docker', 'daytona', 'e2b'])
.default('docker'),
- EXCLUDED_COMPUTE_PROVIDERS: z.string().optional(),
- DOCKER_WORKER_IMAGE: z
+ R_EXCLUDED_COMPUTE_PROVIDERS: z.string().optional(),
+ R_DOCKER_WORKER_IMAGE: z
.string()
.min(1)
.default(DEFAULT_LOCAL_DOCKER_WORKER_IMAGE),
@@ -73,28 +73,30 @@ const serverSchema = {
// arm servers) run the native worker-image variant instead of emulating
// amd64. Only the Docker compute provider consumes this; hosted providers
// (Modal/E2B/Daytona) run amd64 and resolve it from the image manifest.
- DOCKER_WORKER_PLATFORM: z
+ R_DOCKER_WORKER_PLATFORM: z
.string()
.min(1)
.default(process.arch === 'arm64' ? 'linux/arm64' : 'linux/amd64'),
- DOCKER_WORKER_NETWORK: z.string().min(1).optional(),
- DOCKER_WORKER_RELEASE_PATH: z.string().min(1).optional(),
- DOCKER_WORKER_CPU_LIMIT: z.coerce.number().positive().default(2),
- DOCKER_WORKER_MEMORY_LIMIT: dockerSize().default('4g'),
- DOCKER_WORKER_PIDS_LIMIT: z.coerce.number().int().positive().default(512),
- DOCKER_WORKER_DISK_LIMIT: dockerSize().default('20g'),
- DOCKER_WORKER_ALLOW_UNBOUNDED_DISK: optInBoolean(),
- DOCKER_WORKER_LOG_MAX_SIZE: dockerSize().default('10m'),
- DOCKER_WORKER_LOG_MAX_FILES: z.coerce.number().int().positive().default(3),
- DOCKER_WORKER_EGRESS_POLICY: z.enum(['internet', 'none']).default('internet'),
- DOCKER_STANDBY_MAX_COUNT: z.coerce.number().int().nonnegative().default(10),
- DOCKER_STANDBY_MAX_AGE_HOURS: z.coerce
+ R_DOCKER_WORKER_NETWORK: z.string().min(1).optional(),
+ R_DOCKER_WORKER_RELEASE_PATH: z.string().min(1).optional(),
+ R_DOCKER_WORKER_CPU_LIMIT: z.coerce.number().positive().default(2),
+ R_DOCKER_WORKER_MEMORY_LIMIT: dockerSize().default('4g'),
+ R_DOCKER_WORKER_PIDS_LIMIT: z.coerce.number().int().positive().default(512),
+ R_DOCKER_WORKER_DISK_LIMIT: dockerSize().default('20g'),
+ R_DOCKER_WORKER_ALLOW_UNBOUNDED_DISK: optInBoolean(),
+ R_DOCKER_WORKER_LOG_MAX_SIZE: dockerSize().default('10m'),
+ R_DOCKER_WORKER_LOG_MAX_FILES: z.coerce.number().int().positive().default(3),
+ R_DOCKER_WORKER_EGRESS_POLICY: z
+ .enum(['internet', 'none'])
+ .default('internet'),
+ R_DOCKER_STANDBY_MAX_COUNT: z.coerce.number().int().nonnegative().default(10),
+ R_DOCKER_STANDBY_MAX_AGE_HOURS: z.coerce
.number()
.positive()
.max(168)
.default(24),
- BLAXEL_STANDBY_MAX_COUNT: z.coerce.number().int().nonnegative().default(25),
- BLAXEL_STANDBY_MAX_AGE_HOURS: z.coerce
+ R_BLAXEL_STANDBY_MAX_COUNT: z.coerce.number().int().nonnegative().default(25),
+ R_BLAXEL_STANDBY_MAX_AGE_HOURS: z.coerce
.number()
.positive()
.max(168)
@@ -108,7 +110,7 @@ const serverSchema = {
ROOMOTE_FORCE_TELEMETRY: z.string().optional(),
// Release tag baked into published app images by the publish workflow.
RELEASE_VERSION: z.string().min(1).optional(),
- TRPC_URL: z.string().min(1),
+ R_TRPC_URL: z.string().min(1),
R_MODEL: z.string().min(1).optional(),
R_SMALL_MODEL: z.string().min(1).optional(),
R_VISION_MODEL: z.string().min(1).optional(),
@@ -137,21 +139,21 @@ const serverSchema = {
R_GITHUB_APP_PRIVATE_KEY: emptyStringDefault(),
R_GITHUB_CLIENT_ID: emptyStringDefault(),
R_GITHUB_CLIENT_SECRET: emptyStringDefault(),
- GITHUB_MCP_SERVER_URL: z.string().min(1).optional(),
+ R_GITHUB_MCP_SERVER_URL: z.string().min(1).optional(),
R_GITHUB_WEBHOOK_SECRET: emptyStringDefault(),
- GITLAB_WEBHOOK_SECRET: emptyStringDefault(),
- GITLAB_WEBHOOK_SIGNING_TOKEN: emptyStringDefault(),
- WORKER_RELEASE_CHANNEL: z.enum(['stable', 'preview']).optional(),
- WORKER_RELEASE_VERSION: z.string().min(1).optional(),
- SLACK_APP_ID: emptyStringDefault(),
+ R_GITLAB_WEBHOOK_SECRET: emptyStringDefault(),
+ R_GITLAB_WEBHOOK_SIGNING_TOKEN: emptyStringDefault(),
+ R_WORKER_RELEASE_CHANNEL: z.enum(['stable', 'preview']).optional(),
+ R_WORKER_RELEASE_VERSION: z.string().min(1).optional(),
+ R_SLACK_APP_ID: emptyStringDefault(),
R_SLACK_CLIENT_ID: z.string().min(1).optional(),
R_SLACK_CLIENT_SECRET: z.string().min(1).optional(),
- SLACK_REDIRECT_URI: emptyStringDefault(),
- SLACK_AUTH_URI: emptyStringDefault(),
+ R_SLACK_REDIRECT_URI: emptyStringDefault(),
+ R_SLACK_AUTH_URI: emptyStringDefault(),
R_SLACK_SIGNING_SECRET: z.string().min(1).optional(),
- SLACK_API_BASE_URL: z.string().url().default('https://slack.com/api/'),
- SLACK_UNFURL_ALLOWED_DOMAINS: z.string().optional(),
- ROUTER_DEBUG_CHANNEL_ID: z.string().optional(),
+ R_SLACK_API_BASE_URL: z.string().url().default('https://slack.com/api/'),
+ R_SLACK_UNFURL_ALLOWED_DOMAINS: z.string().optional(),
+ R_ROUTER_DEBUG_CHANNEL_ID: z.string().optional(),
// When adding an integration/instance secret below, also add it to
// CONTROL_PLANE_ENV_VAR_NAMES (packages/types/src/control-plane-env-vars.ts)
// unless it is already a `secret` field in a setup catalog, or it leaks into
@@ -164,7 +166,7 @@ const serverSchema = {
R_TEAMS_BOT_OAUTH_SCOPE: z.string().min(1).optional(),
R_TELEGRAM_BOT_TOKEN: z.string().min(1).optional(),
R_TELEGRAM_WEBHOOK_SECRET: z.string().min(1).optional(),
- TELEGRAM_API_BASE_URL: z.string().url().default('https://api.telegram.org'),
+ R_TELEGRAM_API_BASE_URL: z.string().url().default('https://api.telegram.org'),
R_MICROSOFT_CLIENT_ID: z.string().min(1).optional(),
R_MICROSOFT_CLIENT_SECRET: z.string().min(1).optional(),
R_MICROSOFT_TENANT_ID: z.string().min(1).optional(),
@@ -180,8 +182,8 @@ const serverSchema = {
ENCRYPTION_KEY: z.string().min(32),
ARTIFACT_SIGNING_KEY: z.string().min(32),
ARTIFACT_SIGNING_KEY_PREVIOUS: z.string().min(32).optional(),
- API_DEBUG_LOGS: z.string().optional(),
- SLACK_DEBUG_LOGS: z.string().optional(),
+ R_API_DEBUG_LOGS: z.string().optional(),
+ R_SLACK_DEBUG_LOGS: z.string().optional(),
S3_ENDPOINT: z.string().min(1),
S3_PRESIGN_ENDPOINT: z.string().min(1).optional(),
S3_REGION: z.string().min(1),
@@ -189,7 +191,7 @@ const serverSchema = {
S3_SECRET_ACCESS_KEY: z.string().min(1),
S3_BUCKET_ARTIFACTS: z.string().min(1),
S3_AUTO_CREATE_BUCKET: z.string().optional(),
- PREVIEW_PROXY_BASE_URL: emptyStringDefault(),
+ R_PREVIEW_PROXY_BASE_URL: emptyStringDefault(),
WEB_DEV_LOGIN_EMAIL: z.string().optional(),
// Explicit opt-in for the /auth/dev-login backdoor. Required in addition to
// a development app env so implicit-development deployments never expose it.
@@ -225,26 +227,26 @@ const serverSchema = {
.int()
.positive()
.default(3_600_000),
- GITHUB_AUTOMATED_SKIP_REPOS: z.string().optional(),
- GITHUB_AUTOMATED_SKIP_OWNERS: z.string().optional(),
- PREVIEW_DOMAINS: emptyStringDefault(),
+ R_GITHUB_AUTOMATED_SKIP_REPOS: z.string().optional(),
+ R_GITHUB_AUTOMATED_SKIP_OWNERS: z.string().optional(),
+ R_PREVIEW_DOMAINS: emptyStringDefault(),
R_ALLOWED_EMAILS: z.string().optional(),
- PREVIEW_TOKEN_TTL_SECONDS: z.coerce.number().int().positive().default(3600),
- SLACK_API_TIMEOUT_MS: z.coerce.number().int().positive().default(10_000),
+ R_PREVIEW_TOKEN_TTL_SECONDS: z.coerce.number().int().positive().default(3600),
+ R_SLACK_API_TIMEOUT_MS: z.coerce.number().int().positive().default(10_000),
// How long recorded webhook payloads are kept before the WebhookCleanup
// scheduled job (apps/bullmq) deletes them.
- WEBHOOK_RETENTION_DAYS: z.coerce.number().int().positive().default(30),
- API_EXTERNAL_REQUEST_TIMEOUT_MS: z.coerce
+ R_WEBHOOK_RETENTION_DAYS: z.coerce.number().int().positive().default(30),
+ R_API_EXTERNAL_REQUEST_TIMEOUT_MS: z.coerce
.number()
.int()
.positive()
.default(10_000),
- API_SLOW_REQUEST_THRESHOLD_MS: z.coerce
+ R_API_SLOW_REQUEST_THRESHOLD_MS: z.coerce
.number()
.int()
.positive()
.default(5_000),
- API_SLOW_EXTERNAL_REQUEST_THRESHOLD_MS: z.coerce
+ R_API_SLOW_EXTERNAL_REQUEST_THRESHOLD_MS: z.coerce
.number()
.int()
.positive()
@@ -266,7 +268,7 @@ type ServerEnvKey = keyof typeof serverSchema;
const REQUIRED_SERVER_ENV_KEYS = new Set([
'R_APP_URL',
- 'TRPC_URL',
+ 'R_TRPC_URL',
'DATABASE_URL',
'REDIS_URL',
'DASHBOARD_PASSWORD',
@@ -289,7 +291,7 @@ export const SERVICE_REQUIRED_SERVER_ENV_KEYS = {
web: [...REQUIRED_SERVER_ENV_KEYS],
api: [
'R_APP_URL',
- 'TRPC_URL',
+ 'R_TRPC_URL',
'DATABASE_URL',
'REDIS_URL',
'ENCRYPTION_KEY',
@@ -303,7 +305,7 @@ export const SERVICE_REQUIRED_SERVER_ENV_KEYS = {
],
controller: [
'R_APP_URL',
- 'TRPC_URL',
+ 'R_TRPC_URL',
'DATABASE_URL',
'REDIS_URL',
'ENCRYPTION_KEY',
@@ -350,10 +352,14 @@ function buildServiceServerSchema(
const clientSchema = {};
/** Keys whose runtime resolution requires more than a plain `resolve(key)`. */
-const OVERRIDE_KEYS = new Set(['NODE_ENV', 'APP_ENV', 'SLACK_API_TIMEOUT_MS']);
+const OVERRIDE_KEYS = new Set([
+ 'NODE_ENV',
+ 'APP_ENV',
+ 'R_SLACK_API_TIMEOUT_MS',
+]);
const OPTIONAL_NON_EMPTY_KEYS = new Set([
- 'DOCKER_WORKER_IMAGE',
+ 'R_DOCKER_WORKER_IMAGE',
'R_APP_ENV',
'R_PUBLIC_URL',
'R_APP_URL',
@@ -362,13 +368,13 @@ const OPTIONAL_NON_EMPTY_KEYS = new Set([
'SANDBOX_OIDC_PRIVATE_KEY',
'SANDBOX_OIDC_PUBLIC_KEY',
'SANDBOX_OIDC_PUBLIC_KEY_SECONDARY',
- 'GITHUB_MCP_SERVER_URL',
- 'WORKER_RELEASE_CHANNEL',
- 'WORKER_RELEASE_VERSION',
+ 'R_GITHUB_MCP_SERVER_URL',
+ 'R_WORKER_RELEASE_CHANNEL',
+ 'R_WORKER_RELEASE_VERSION',
'RELEASE_VERSION',
'R_PING_BASE_URL',
- 'SLACK_UNFURL_ALLOWED_DOMAINS',
- 'ROUTER_DEBUG_CHANNEL_ID',
+ 'R_SLACK_UNFURL_ALLOWED_DOMAINS',
+ 'R_ROUTER_DEBUG_CHANNEL_ID',
'R_TEAMS_BOT_APP_ID',
'R_TEAMS_BOT_APP_PASSWORD',
'R_TEAMS_BOT_TENANT_ID',
@@ -389,8 +395,8 @@ const OPTIONAL_NON_EMPTY_KEYS = new Set([
'R_LINEAR_REDIRECT_URI',
'ARTIFACT_SIGNING_KEY_PREVIOUS',
'SETUP_TOKEN',
- 'API_DEBUG_LOGS',
- 'SLACK_DEBUG_LOGS',
+ 'R_API_DEBUG_LOGS',
+ 'R_SLACK_DEBUG_LOGS',
'S3_ENDPOINT',
'S3_PRESIGN_ENDPOINT',
'S3_REGION',
@@ -422,10 +428,41 @@ const OPTIONAL_NON_EMPTY_KEYS = new Set([
'E2B_DOMAIN',
'E2B_TEMPLATE_ID',
'E2B_MAX_SANDBOX_TIMEOUT_MS',
- 'DOCKER_WORKER_NETWORK',
- 'DOCKER_WORKER_RELEASE_PATH',
- 'GITHUB_AUTOMATED_SKIP_REPOS',
- 'GITHUB_AUTOMATED_SKIP_OWNERS',
+ 'R_DOCKER_WORKER_NETWORK',
+ 'R_DOCKER_WORKER_RELEASE_PATH',
+ 'R_EXCLUDED_COMPUTE_PROVIDERS',
+ 'R_PREVIEW_PROXY_BASE_URL',
+ 'R_PREVIEW_DOMAINS',
+ 'R_PREVIEW_TOKEN_TTL_SECONDS',
+ 'R_SLACK_APP_ID',
+ 'R_SLACK_REDIRECT_URI',
+ 'R_SLACK_AUTH_URI',
+ 'R_SLACK_API_BASE_URL',
+ 'R_TELEGRAM_API_BASE_URL',
+ 'R_GITLAB_BASE_URL',
+ 'R_GITLAB_CLIENT_ID',
+ 'R_GITLAB_CLIENT_SECRET',
+ 'R_GITLAB_WEBHOOK_SIGNING_TOKEN',
+ 'R_GITLAB_WEBHOOK_SECRET',
+ 'R_GITEA_BASE_URL',
+ 'R_GITEA_USERNAME',
+ 'R_GITEA_CLIENT_ID',
+ 'R_GITEA_CLIENT_SECRET',
+ 'R_GITEA_WEBHOOK_SECRET',
+ 'R_ADO_ORGANIZATION',
+ 'R_ADO_BASE_URL',
+ 'R_ADO_USERNAME',
+ 'R_ADO_CLIENT_ID',
+ 'R_ADO_CLIENT_SECRET',
+ 'R_ADO_TENANT_ID',
+ 'R_ADO_WEBHOOK_SECRET',
+ 'R_BITBUCKET_USERNAME',
+ 'R_BITBUCKET_BASE_URL',
+ 'R_BITBUCKET_CLIENT_ID',
+ 'R_BITBUCKET_CLIENT_SECRET',
+ 'R_BITBUCKET_WEBHOOK_SECRET',
+ 'R_GITHUB_AUTOMATED_SKIP_REPOS',
+ 'R_GITHUB_AUTOMATED_SKIP_OWNERS',
'R_MODEL',
'R_SMALL_MODEL',
'R_VISION_MODEL',
@@ -652,15 +689,15 @@ function buildRoomoteRuntimeEnv(
// Published app images bake RELEASE_VERSION, and the publish workflow
// pushes the worker image with the identical tag. Deriving the worker
// image from it spares self-host operators a manual multi-place version
- // bump on every upgrade; an explicit DOCKER_WORKER_IMAGE always wins, and
+ // bump on every upgrade; an explicit R_DOCKER_WORKER_IMAGE always wins, and
// local/self-host builds fall through to the schema default.
const resolvedDockerWorkerImage = resolveEffectiveDockerWorkerImage({
- DOCKER_WORKER_IMAGE: resolve('DOCKER_WORKER_IMAGE'),
+ R_DOCKER_WORKER_IMAGE: resolve('R_DOCKER_WORKER_IMAGE'),
RELEASE_VERSION: resolve('RELEASE_VERSION'),
ROOMOTE_WORKER_IMAGE_REPO: resolve('ROOMOTE_WORKER_IMAGE_REPO'),
});
- env.DOCKER_WORKER_IMAGE =
+ env.R_DOCKER_WORKER_IMAGE =
resolvedDockerWorkerImage ?? DEFAULT_LOCAL_DOCKER_WORKER_IMAGE;
// Keys with non-trivial resolution logic:
@@ -668,15 +705,15 @@ function buildRoomoteRuntimeEnv(
env.NODE_ENV = nodeEnv;
// APP_ENV is derived from multiple Vercel env vars, not a single key lookup.
env.APP_ENV = resolveAppEnv(processEnv);
- env.SLACK_API_TIMEOUT_MS =
- resolve('SLACK_API_TIMEOUT_MS') ??
- resolve('API_EXTERNAL_REQUEST_TIMEOUT_MS');
+ env.R_SLACK_API_TIMEOUT_MS =
+ resolve('R_SLACK_API_TIMEOUT_MS') ??
+ resolve('R_API_EXTERNAL_REQUEST_TIMEOUT_MS');
const appEnv = resolveAppEnv(processEnv);
if (nodeEnv !== 'production' && appEnv === 'development') {
env.R_APP_URL ??= getDefaultRoomoteAppUrl(appEnv);
- env.TRPC_URL ??= getDefaultTrpcUrl(appEnv);
+ env.R_TRPC_URL ??= getDefaultTrpcUrl(appEnv);
env.DATABASE_URL ??=
nodeEnv === 'test'
? 'postgres://postgres:password@localhost:15432/roomote_test'
@@ -686,8 +723,8 @@ function buildRoomoteRuntimeEnv(
env.DASHBOARD_PASSWORD ??= LOCAL_DASHBOARD_PASSWORD;
env.ENCRYPTION_KEY ??= LOCAL_ENCRYPTION_KEY;
env.ARTIFACT_SIGNING_KEY ??= LOCAL_ARTIFACT_SIGNING_KEY;
- env.PREVIEW_PROXY_BASE_URL ??= getDefaultPreviewProxyBaseUrl(appEnv);
- env.PREVIEW_DOMAINS ??= LOCAL_PREVIEW_DOMAINS;
+ env.R_PREVIEW_PROXY_BASE_URL ??= getDefaultPreviewProxyBaseUrl(appEnv);
+ env.R_PREVIEW_DOMAINS ??= LOCAL_PREVIEW_DOMAINS;
env.S3_ENDPOINT ??= LOCAL_S3_ENDPOINT;
env.S3_PRESIGN_ENDPOINT ??= LOCAL_S3_PRESIGN_ENDPOINT;
env.S3_REGION ??= LOCAL_S3_REGION;
@@ -697,10 +734,10 @@ function buildRoomoteRuntimeEnv(
}
// Slack rejects the whole account-linking DM (invalid_blocks) when the
- // "Link accounts" button URL is not absolute, so an unset SLACK_AUTH_URI
+ // "Link accounts" button URL is not absolute, so an unset R_SLACK_AUTH_URI
// must fall back to the web app's linking route rather than empty string.
- if (!env.SLACK_AUTH_URI && env.R_APP_URL) {
- env.SLACK_AUTH_URI = `${env.R_APP_URL.replace(/\/+$/, '')}/api/slack/auth`;
+ if (!env.R_SLACK_AUTH_URI && env.R_APP_URL) {
+ env.R_SLACK_AUTH_URI = `${env.R_APP_URL.replace(/\/+$/, '')}/api/slack/auth`;
}
return env;
diff --git a/packages/gitea/src/__tests__/api.test.ts b/packages/gitea/src/__tests__/api.test.ts
index eb75199b..89cd3363 100644
--- a/packages/gitea/src/__tests__/api.test.ts
+++ b/packages/gitea/src/__tests__/api.test.ts
@@ -89,14 +89,14 @@ function makeTaskRun(payload: TaskRun['payload']): TaskRun {
describe('Gitea API helpers', () => {
const originalGiteaToken = process.env.GITEA_TOKEN;
- const originalGiteaBaseUrl = process.env.GITEA_BASE_URL;
- const originalGiteaUsername = process.env.GITEA_USERNAME;
+ const originalGiteaBaseUrl = process.env.R_GITEA_BASE_URL;
+ const originalGiteaUsername = process.env.R_GITEA_USERNAME;
beforeEach(() => {
vi.clearAllMocks();
process.env.GITEA_TOKEN = 'gitea_deployment_token';
- process.env.GITEA_BASE_URL = 'https://git.example.com/';
- delete process.env.GITEA_USERNAME;
+ process.env.R_GITEA_BASE_URL = 'https://git.example.com/';
+ delete process.env.R_GITEA_USERNAME;
mockEnvironmentVariablesFindMany.mockResolvedValue([]);
mockEnvironmentsFindFirst.mockResolvedValue(null);
mockRepositoriesFindMany.mockResolvedValue([
@@ -114,15 +114,15 @@ describe('Gitea API helpers', () => {
}
if (originalGiteaBaseUrl === undefined) {
- delete process.env.GITEA_BASE_URL;
+ delete process.env.R_GITEA_BASE_URL;
} else {
- process.env.GITEA_BASE_URL = originalGiteaBaseUrl;
+ process.env.R_GITEA_BASE_URL = originalGiteaBaseUrl;
}
if (originalGiteaUsername === undefined) {
- delete process.env.GITEA_USERNAME;
+ delete process.env.R_GITEA_USERNAME;
} else {
- process.env.GITEA_USERNAME = originalGiteaUsername;
+ process.env.R_GITEA_USERNAME = originalGiteaUsername;
}
});
@@ -208,7 +208,9 @@ describe('Gitea API helpers', () => {
token: 'gitea_test',
baseUrl: '',
}),
- ).rejects.toThrow('GITEA_BASE_URL is required to sync Gitea repositories.');
+ ).rejects.toThrow(
+ 'R_GITEA_BASE_URL is required to sync Gitea repositories.',
+ );
});
it('maps Gitea repository fields into provider-tagged repository rows', () => {
diff --git a/packages/gitea/src/api.ts b/packages/gitea/src/api.ts
index ed740147..9c76fbb2 100644
--- a/packages/gitea/src/api.ts
+++ b/packages/gitea/src/api.ts
@@ -104,7 +104,7 @@ function normalizeBaseUrl(baseUrl: string): string {
const trimmed = baseUrl.trim().replace(/\/+$/, '');
if (!trimmed) {
- throw new Error('GITEA_BASE_URL cannot be empty.');
+ throw new Error('R_GITEA_BASE_URL cannot be empty.');
}
return new URL(trimmed).toString().replace(/\/+$/, '');
@@ -121,12 +121,12 @@ let cachedGiteaDeploymentUser: {
} | null = null;
export async function resolveGiteaBaseUrl(): Promise {
- const baseUrl = await resolveDeploymentEnvVar('GITEA_BASE_URL');
+ const baseUrl = await resolveDeploymentEnvVar('R_GITEA_BASE_URL');
return baseUrl ? normalizeBaseUrl(baseUrl) : null;
}
export async function resolveGiteaUsername(): Promise {
- return resolveDeploymentEnvVar('GITEA_USERNAME');
+ return resolveDeploymentEnvVar('R_GITEA_USERNAME');
}
export function buildGiteaApiBaseUrl(baseUrl: string): string {
@@ -279,7 +279,7 @@ export async function listGiteaRepositories({
const resolvedBaseUrl = baseUrl ?? (await resolveGiteaBaseUrl());
if (!resolvedBaseUrl?.trim() && !apiBaseUrl?.trim()) {
- throw new Error('GITEA_BASE_URL is required to sync Gitea repositories.');
+ throw new Error('R_GITEA_BASE_URL is required to sync Gitea repositories.');
}
const resolvedApiBaseUrl =
@@ -389,7 +389,7 @@ export async function syncGiteaRepositories({
const resolvedBaseUrl = baseUrl ?? (await resolveGiteaBaseUrl());
if (!resolvedBaseUrl?.trim()) {
- throw new Error('GITEA_BASE_URL is required to sync Gitea repositories.');
+ throw new Error('R_GITEA_BASE_URL is required to sync Gitea repositories.');
}
const existingIds = (
@@ -580,7 +580,7 @@ export async function getGiteaAuthenticatedUser({
if (!resolvedBaseUrl?.trim() && !apiBaseUrl?.trim()) {
throw new Error(
- 'GITEA_BASE_URL is required for Gitea source control jobs.',
+ 'R_GITEA_BASE_URL is required for Gitea source control jobs.',
);
}
@@ -685,7 +685,7 @@ export async function createGiteaPullRequestComment({
if (!resolvedBaseUrl?.trim() && !apiBaseUrl?.trim()) {
throw new Error(
- 'GITEA_BASE_URL is required to create Gitea pull request comments.',
+ 'R_GITEA_BASE_URL is required to create Gitea pull request comments.',
);
}
@@ -855,7 +855,9 @@ export async function ensureGiteaWebhooksForRepositories({
const resolvedBaseUrl = baseUrl ?? (await resolveGiteaBaseUrl());
if (!resolvedBaseUrl?.trim() && !apiBaseUrl?.trim()) {
- throw new Error('GITEA_BASE_URL is required to configure Gitea webhooks.');
+ throw new Error(
+ 'R_GITEA_BASE_URL is required to configure Gitea webhooks.',
+ );
}
const resolvedApiBaseUrl =
@@ -989,7 +991,9 @@ export async function removeGiteaWebhooksForRepositories({
const resolvedBaseUrl = baseUrl ?? (await resolveGiteaBaseUrl());
if (!resolvedBaseUrl?.trim() && !apiBaseUrl?.trim()) {
- throw new Error('GITEA_BASE_URL is required to configure Gitea webhooks.');
+ throw new Error(
+ 'R_GITEA_BASE_URL is required to configure Gitea webhooks.',
+ );
}
const resolvedApiBaseUrl =
@@ -1055,7 +1059,7 @@ export async function createTaskRunGiteaCredentials(
if (!baseUrl?.trim()) {
throw new Error(
- 'GITEA_BASE_URL is required for Gitea source control jobs.',
+ 'R_GITEA_BASE_URL is required for Gitea source control jobs.',
);
}
diff --git a/packages/github/src/__tests__/is-repo-skipped.test.ts b/packages/github/src/__tests__/is-repo-skipped.test.ts
index 73514c19..71169438 100644
--- a/packages/github/src/__tests__/is-repo-skipped.test.ts
+++ b/packages/github/src/__tests__/is-repo-skipped.test.ts
@@ -3,15 +3,15 @@ describe('isRepoSkipped', () => {
vi.resetModules();
});
- it('should return false when GITHUB_AUTOMATED_SKIP_REPOS is not set', async () => {
+ it('should return false when R_GITHUB_AUTOMATED_SKIP_REPOS is not set', async () => {
vi.doMock('@roomote/env', async (importOriginal) => {
const actual = await importOriginal();
return {
...actual,
Env: {
- GITHUB_AUTOMATED_SKIP_REPOS: undefined,
- GITHUB_AUTOMATED_SKIP_OWNERS: undefined,
+ R_GITHUB_AUTOMATED_SKIP_REPOS: undefined,
+ R_GITHUB_AUTOMATED_SKIP_OWNERS: undefined,
},
};
});
@@ -20,15 +20,15 @@ describe('isRepoSkipped', () => {
expect(isRepoSkipped('Roomote/example-app')).toBe(false);
});
- it('should return false when GITHUB_AUTOMATED_SKIP_REPOS is empty', async () => {
+ it('should return false when R_GITHUB_AUTOMATED_SKIP_REPOS is empty', async () => {
vi.doMock('@roomote/env', async (importOriginal) => {
const actual = await importOriginal();
return {
...actual,
Env: {
- GITHUB_AUTOMATED_SKIP_REPOS: '',
- GITHUB_AUTOMATED_SKIP_OWNERS: '',
+ R_GITHUB_AUTOMATED_SKIP_REPOS: '',
+ R_GITHUB_AUTOMATED_SKIP_OWNERS: '',
},
};
});
@@ -44,9 +44,9 @@ describe('isRepoSkipped', () => {
return {
...actual,
Env: {
- GITHUB_AUTOMATED_SKIP_REPOS:
+ R_GITHUB_AUTOMATED_SKIP_REPOS:
'Roomote/example-app,Roomote/example-app,Roomote/example-cloud',
- GITHUB_AUTOMATED_SKIP_OWNERS: undefined,
+ R_GITHUB_AUTOMATED_SKIP_OWNERS: undefined,
},
};
});
@@ -64,8 +64,8 @@ describe('isRepoSkipped', () => {
return {
...actual,
Env: {
- GITHUB_AUTOMATED_SKIP_REPOS: 'Roomote/example-app',
- GITHUB_AUTOMATED_SKIP_OWNERS: undefined,
+ R_GITHUB_AUTOMATED_SKIP_REPOS: 'Roomote/example-app',
+ R_GITHUB_AUTOMATED_SKIP_OWNERS: undefined,
},
};
});
@@ -81,8 +81,8 @@ describe('isRepoSkipped', () => {
return {
...actual,
Env: {
- GITHUB_AUTOMATED_SKIP_REPOS: 'Roomote/example-app',
- GITHUB_AUTOMATED_SKIP_OWNERS: undefined,
+ R_GITHUB_AUTOMATED_SKIP_REPOS: 'Roomote/example-app',
+ R_GITHUB_AUTOMATED_SKIP_OWNERS: undefined,
},
};
});
@@ -99,9 +99,9 @@ describe('isRepoSkipped', () => {
return {
...actual,
Env: {
- GITHUB_AUTOMATED_SKIP_REPOS:
+ R_GITHUB_AUTOMATED_SKIP_REPOS:
' Roomote/example-app , Roomote/example-app ',
- GITHUB_AUTOMATED_SKIP_OWNERS: undefined,
+ R_GITHUB_AUTOMATED_SKIP_OWNERS: undefined,
},
};
});
@@ -118,8 +118,8 @@ describe('isRepoSkipped', () => {
return {
...actual,
Env: {
- GITHUB_AUTOMATED_SKIP_REPOS: undefined,
- GITHUB_AUTOMATED_SKIP_OWNERS: 'Roomote',
+ R_GITHUB_AUTOMATED_SKIP_REPOS: undefined,
+ R_GITHUB_AUTOMATED_SKIP_OWNERS: 'Roomote',
},
};
});
@@ -137,8 +137,8 @@ describe('isRepoSkipped', () => {
return {
...actual,
Env: {
- GITHUB_AUTOMATED_SKIP_REPOS: undefined,
- GITHUB_AUTOMATED_SKIP_OWNERS: ' Roomote , SomeOtherOrg ',
+ R_GITHUB_AUTOMATED_SKIP_REPOS: undefined,
+ R_GITHUB_AUTOMATED_SKIP_OWNERS: ' Roomote , SomeOtherOrg ',
},
};
});
diff --git a/packages/github/src/is-repo-skipped.ts b/packages/github/src/is-repo-skipped.ts
index 8d1ff2a2..fe2b5a9d 100644
--- a/packages/github/src/is-repo-skipped.ts
+++ b/packages/github/src/is-repo-skipped.ts
@@ -4,10 +4,10 @@ import { Env } from '@roomote/env';
* Comma-separated list of repository full names (e.g. "owner/repo") that
* should be skipped for automated GitHub processing.
*
- * The value comes from the optional `GITHUB_AUTOMATED_SKIP_REPOS` env var.
+ * The value comes from the optional `R_GITHUB_AUTOMATED_SKIP_REPOS` env var.
*/
const skippedRepos: Set = new Set(
- (Env.GITHUB_AUTOMATED_SKIP_REPOS ?? '')
+ (Env.R_GITHUB_AUTOMATED_SKIP_REPOS ?? '')
.split(',')
.map((repo) => repo.trim().toLowerCase())
.filter(Boolean),
@@ -17,10 +17,10 @@ const skippedRepos: Set = new Set(
* Comma-separated list of repository owners (e.g. "Roomote") whose repos
* should be skipped for automated GitHub processing.
*
- * The value comes from the optional `GITHUB_AUTOMATED_SKIP_OWNERS` env var.
+ * The value comes from the optional `R_GITHUB_AUTOMATED_SKIP_OWNERS` env var.
*/
const skippedOwners: Set = new Set(
- (Env.GITHUB_AUTOMATED_SKIP_OWNERS ?? '')
+ (Env.R_GITHUB_AUTOMATED_SKIP_OWNERS ?? '')
.split(',')
.map((owner) => owner.trim().toLowerCase())
.filter(Boolean),
diff --git a/packages/gitlab/src/__tests__/api.test.ts b/packages/gitlab/src/__tests__/api.test.ts
index 3ac5c335..39e16ba2 100644
--- a/packages/gitlab/src/__tests__/api.test.ts
+++ b/packages/gitlab/src/__tests__/api.test.ts
@@ -91,24 +91,24 @@ function makeTaskRun(payload: TaskRun['payload']): TaskRun {
}
describe('resolveGitLabBaseUrl', () => {
- const originalBaseUrl = process.env.GITLAB_BASE_URL;
+ const originalBaseUrl = process.env.R_GITLAB_BASE_URL;
afterEach(() => {
if (originalBaseUrl === undefined) {
- delete process.env.GITLAB_BASE_URL;
+ delete process.env.R_GITLAB_BASE_URL;
} else {
- process.env.GITLAB_BASE_URL = originalBaseUrl;
+ process.env.R_GITLAB_BASE_URL = originalBaseUrl;
}
});
- it('defaults to gitlab.com when GITLAB_BASE_URL is not set', async () => {
- delete process.env.GITLAB_BASE_URL;
+ it('defaults to gitlab.com when R_GITLAB_BASE_URL is not set', async () => {
+ delete process.env.R_GITLAB_BASE_URL;
await expect(resolveGitLabBaseUrl()).resolves.toBe('https://gitlab.com');
});
- it('normalizes a self-managed GITLAB_BASE_URL by trimming trailing slashes', async () => {
- process.env.GITLAB_BASE_URL = 'https://gitlab.example.com/';
+ it('normalizes a self-managed R_GITLAB_BASE_URL by trimming trailing slashes', async () => {
+ process.env.R_GITLAB_BASE_URL = 'https://gitlab.example.com/';
await expect(resolveGitLabBaseUrl()).resolves.toBe(
'https://gitlab.example.com',
@@ -131,13 +131,13 @@ describe('buildGitLabApiBaseUrl', () => {
});
describe('listGitLabProjects', () => {
- const originalBaseUrl = process.env.GITLAB_BASE_URL;
+ const originalBaseUrl = process.env.R_GITLAB_BASE_URL;
afterEach(() => {
if (originalBaseUrl === undefined) {
- delete process.env.GITLAB_BASE_URL;
+ delete process.env.R_GITLAB_BASE_URL;
} else {
- process.env.GITLAB_BASE_URL = originalBaseUrl;
+ process.env.R_GITLAB_BASE_URL = originalBaseUrl;
}
});
@@ -202,8 +202,8 @@ describe('listGitLabProjects', () => {
);
});
- it('derives the API base from GITLAB_BASE_URL for self-managed instances', async () => {
- process.env.GITLAB_BASE_URL = 'https://gitlab.example.com';
+ it('derives the API base from R_GITLAB_BASE_URL for self-managed instances', async () => {
+ process.env.R_GITLAB_BASE_URL = 'https://gitlab.example.com';
const fetchMock = vi
.fn()
@@ -279,12 +279,12 @@ describe('buildGitLabRepositoryValues', () => {
describe('createTaskRunScopedGitLabTokens', () => {
const originalGitLabToken = process.env.GITLAB_TOKEN;
- const originalGitLabBaseUrl = process.env.GITLAB_BASE_URL;
+ const originalGitLabBaseUrl = process.env.R_GITLAB_BASE_URL;
beforeEach(() => {
vi.clearAllMocks();
process.env.GITLAB_TOKEN = 'glpat_deployment_token';
- delete process.env.GITLAB_BASE_URL;
+ delete process.env.R_GITLAB_BASE_URL;
mockEnvironmentVariablesFindMany.mockResolvedValue([]);
mockEnvironmentsFindFirst.mockResolvedValue(null);
mockRepositoriesFindMany.mockResolvedValue([
@@ -303,9 +303,9 @@ describe('createTaskRunScopedGitLabTokens', () => {
}
if (originalGitLabBaseUrl === undefined) {
- delete process.env.GITLAB_BASE_URL;
+ delete process.env.R_GITLAB_BASE_URL;
} else {
- process.env.GITLAB_BASE_URL = originalGitLabBaseUrl;
+ process.env.R_GITLAB_BASE_URL = originalGitLabBaseUrl;
}
});
@@ -363,8 +363,8 @@ describe('createTaskRunScopedGitLabTokens', () => {
);
});
- it('mints scoped tokens against a self-managed GITLAB_BASE_URL with the self-managed credential host', async () => {
- process.env.GITLAB_BASE_URL = 'https://gitlab.example.com';
+ it('mints scoped tokens against a self-managed R_GITLAB_BASE_URL with the self-managed credential host', async () => {
+ process.env.R_GITLAB_BASE_URL = 'https://gitlab.example.com';
const fetchMock = vi.fn().mockResolvedValue(
new Response(
diff --git a/packages/gitlab/src/api.ts b/packages/gitlab/src/api.ts
index 8f60d450..4f090536 100644
--- a/packages/gitlab/src/api.ts
+++ b/packages/gitlab/src/api.ts
@@ -112,7 +112,7 @@ function normalizeBaseUrl(baseUrl: string): string {
const trimmed = baseUrl.trim().replace(/\/+$/, '');
if (!trimmed) {
- throw new Error('GITLAB_BASE_URL cannot be empty.');
+ throw new Error('R_GITLAB_BASE_URL cannot be empty.');
}
return new URL(trimmed).toString().replace(/\/+$/, '');
@@ -202,7 +202,7 @@ export async function createGitLabMergeRequestNote({
}
export async function resolveGitLabBaseUrl(): Promise {
- const baseUrl = await resolveDeploymentEnvVar('GITLAB_BASE_URL');
+ const baseUrl = await resolveDeploymentEnvVar('R_GITLAB_BASE_URL');
return normalizeBaseUrl(baseUrl ?? DEFAULT_GITLAB_BASE_URL);
}
diff --git a/packages/sdk/src/client/index.ts b/packages/sdk/src/client/index.ts
index fd939b38..cac80756 100644
--- a/packages/sdk/src/client/index.ts
+++ b/packages/sdk/src/client/index.ts
@@ -57,7 +57,7 @@ export const sdk = {
export interface CreateClientOptions {
/**
* The base URL for the tRPC API.
- * Defaults to TRPC_URL env var or http://localhost:3001
+ * Defaults to R_TRPC_URL env var or http://localhost:3001
*/
url?: string;
/**
@@ -323,7 +323,7 @@ export function createWorkerFetchWithRetry(
* });
*/
export function createClient(options: CreateClientOptions = {}) {
- const { url = process.env.TRPC_URL ?? 'http://localhost:3001', headers } =
+ const { url = process.env.R_TRPC_URL ?? 'http://localhost:3001', headers } =
options;
return createTRPCProxyClient({
@@ -340,7 +340,7 @@ export function createClient(options: CreateClientOptions = {}) {
function createWorkerLinkOptions() {
return {
url: resolveApiUrl(
- process.env.TRPC_URL ?? 'http://localhost:3001',
+ process.env.R_TRPC_URL ?? 'http://localhost:3001',
'/trpc',
),
transformer: superjson,
diff --git a/packages/sdk/src/server/automations/__tests__/conflict-scan.test.ts b/packages/sdk/src/server/automations/__tests__/conflict-scan.test.ts
index 3faae0a7..43dc849c 100644
--- a/packages/sdk/src/server/automations/__tests__/conflict-scan.test.ts
+++ b/packages/sdk/src/server/automations/__tests__/conflict-scan.test.ts
@@ -183,7 +183,7 @@ describe('conflictScanJob', () => {
});
});
- it('skips repos in GITHUB_AUTOMATED_SKIP_REPOS before scanning PRs', async () => {
+ it('skips repos in R_GITHUB_AUTOMATED_SKIP_REPOS before scanning PRs', async () => {
mockIsRepoSkipped.mockReturnValue(true);
await conflictScanJob();
diff --git a/packages/sdk/src/server/lib/pull-requests/source-control-pull-request-reads.ts b/packages/sdk/src/server/lib/pull-requests/source-control-pull-request-reads.ts
index 7a8bd158..67802223 100644
--- a/packages/sdk/src/server/lib/pull-requests/source-control-pull-request-reads.ts
+++ b/packages/sdk/src/server/lib/pull-requests/source-control-pull-request-reads.ts
@@ -1104,7 +1104,9 @@ async function resolveGiteaReadContext(
const baseUrl = await resolveGiteaBaseUrl();
if (!baseUrl) {
- throw new Error('GITEA_BASE_URL is required to read Gitea pull requests.');
+ throw new Error(
+ 'R_GITEA_BASE_URL is required to read Gitea pull requests.',
+ );
}
const [owner, repo] = splitRepositoryFullName(repository.fullName, provider);
@@ -1313,7 +1315,7 @@ async function resolveBitbucketReadContext(
const username = await resolveBitbucketUsername();
if (!username) {
throw new Error(
- 'BITBUCKET_USERNAME is required to read Bitbucket pull requests.',
+ 'R_BITBUCKET_USERNAME is required to read Bitbucket pull requests.',
);
}
diff --git a/packages/sdk/src/server/lib/pull-requests/source-control-pull-request-writes.ts b/packages/sdk/src/server/lib/pull-requests/source-control-pull-request-writes.ts
index d5164624..49c18e84 100644
--- a/packages/sdk/src/server/lib/pull-requests/source-control-pull-request-writes.ts
+++ b/packages/sdk/src/server/lib/pull-requests/source-control-pull-request-writes.ts
@@ -1020,7 +1020,9 @@ async function resolveGiteaWriteContext(
const baseUrl = await resolveGiteaBaseUrl();
if (!baseUrl) {
- throw new Error('GITEA_BASE_URL is required to write Gitea pull requests.');
+ throw new Error(
+ 'R_GITEA_BASE_URL is required to write Gitea pull requests.',
+ );
}
const [owner, repo] = splitRepositoryFullName(repository.fullName, provider);
@@ -1270,7 +1272,7 @@ async function resolveBitbucketWriteContext(
const username = await resolveBitbucketUsername();
if (!username) {
throw new Error(
- 'BITBUCKET_USERNAME is required to write Bitbucket pull requests.',
+ 'R_BITBUCKET_USERNAME is required to write Bitbucket pull requests.',
);
}
diff --git a/packages/sdk/src/server/lib/pull-requests/source-control-pull-requests.ts b/packages/sdk/src/server/lib/pull-requests/source-control-pull-requests.ts
index 706ac8ea..dd854056 100644
--- a/packages/sdk/src/server/lib/pull-requests/source-control-pull-requests.ts
+++ b/packages/sdk/src/server/lib/pull-requests/source-control-pull-requests.ts
@@ -692,7 +692,7 @@ async function createOrUpdateGiteaPullRequest({
const baseUrl = await resolveGiteaBaseUrl();
if (!baseUrl) {
throw new Error(
- 'GITEA_BASE_URL is required to create Gitea pull requests.',
+ 'R_GITEA_BASE_URL is required to create Gitea pull requests.',
);
}
@@ -806,7 +806,7 @@ async function createOrUpdateBitbucketPullRequest({
const username = await resolveBitbucketUsername();
if (!username) {
throw new Error(
- 'BITBUCKET_USERNAME is required to create Bitbucket pull requests.',
+ 'R_BITBUCKET_USERNAME is required to create Bitbucket pull requests.',
);
}
diff --git a/packages/sdk/src/server/lib/task-runs/__tests__/dequeue-helpers.test.ts b/packages/sdk/src/server/lib/task-runs/__tests__/dequeue-helpers.test.ts
index e72d8b6e..a05b6e53 100644
--- a/packages/sdk/src/server/lib/task-runs/__tests__/dequeue-helpers.test.ts
+++ b/packages/sdk/src/server/lib/task-runs/__tests__/dequeue-helpers.test.ts
@@ -471,8 +471,8 @@ describe('redactControlPlaneEnvVars', () => {
DATABASE_URL: 'postgres://x',
S3_SECRET_ACCESS_KEY: 's3',
// Derived from the source-control secret catalog.
- GITLAB_WEBHOOK_SECRET: 'gl-webhook',
- GITLAB_CLIENT_SECRET: 'gl-client',
+ R_GITLAB_WEBHOOK_SECRET: 'gl-webhook',
+ R_GITLAB_CLIENT_SECRET: 'gl-client',
// Derived from the sign-in auth catalog.
R_MICROSOFT_CLIENT_SECRET: 'ms',
// Teams bot secret (hand-listed bot integration).
diff --git a/packages/slack/evals/run-slack-scenario.ts b/packages/slack/evals/run-slack-scenario.ts
index fa3db706..5278297c 100644
--- a/packages/slack/evals/run-slack-scenario.ts
+++ b/packages/slack/evals/run-slack-scenario.ts
@@ -6,7 +6,7 @@
* scripts/judge-criteria.ts).
*
* Prerequisites: local dev stack running (pnpm dev) with
- * SLACK_API_BASE_URL pointing at the mock port used here, and the mock
+ * R_SLACK_API_BASE_URL pointing at the mock port used here, and the mock
* Slack installation/user mapping seeded (see
* .claude/skills/mock-slack-testing/SKILL.md).
*
@@ -106,11 +106,11 @@ console.log(`mock slack listening at ${baseUrl} -> ${values.webhook}`);
const expectedBase = `${baseUrl}/api/`;
if (
- process.env.SLACK_API_BASE_URL &&
- process.env.SLACK_API_BASE_URL !== expectedBase
+ process.env.R_SLACK_API_BASE_URL &&
+ process.env.R_SLACK_API_BASE_URL !== expectedBase
) {
console.warn(
- `WARNING: SLACK_API_BASE_URL=${process.env.SLACK_API_BASE_URL} does not match ${expectedBase}; ` +
+ `WARNING: R_SLACK_API_BASE_URL=${process.env.R_SLACK_API_BASE_URL} does not match ${expectedBase}; ` +
`the API server may post replies somewhere else.`,
);
}
diff --git a/packages/slack/scripts/run-mock-slack.ts b/packages/slack/scripts/run-mock-slack.ts
index 698175db..a2688d1b 100644
--- a/packages/slack/scripts/run-mock-slack.ts
+++ b/packages/slack/scripts/run-mock-slack.ts
@@ -201,7 +201,7 @@ async function main() {
console.info(`Mock Slack API listening at ${baseUrl}`);
console.info(
- `Set SLACK_API_BASE_URL=${baseUrl}/api/ in the Roomote services you want to point at the harness.`,
+ `Set R_SLACK_API_BASE_URL=${baseUrl}/api/ in the Roomote services you want to point at the harness.`,
);
if (config.replay?.length) {
diff --git a/packages/slack/src/__tests__/logging.test.ts b/packages/slack/src/__tests__/logging.test.ts
index 860d1f40..48cf523a 100644
--- a/packages/slack/src/__tests__/logging.test.ts
+++ b/packages/slack/src/__tests__/logging.test.ts
@@ -2,16 +2,16 @@ const { envMock } = vi.hoisted(() => ({
envMock: {
APP_ENV: 'production',
NODE_ENV: 'production',
- API_DEBUG_LOGS: undefined as string | undefined,
- SLACK_DEBUG_LOGS: undefined as string | undefined,
+ R_API_DEBUG_LOGS: undefined as string | undefined,
+ R_SLACK_DEBUG_LOGS: undefined as string | undefined,
},
}));
async function importLoggingModule(envOverrides?: {
APP_ENV?: string;
NODE_ENV?: string;
- API_DEBUG_LOGS?: string | undefined;
- SLACK_DEBUG_LOGS?: string | undefined;
+ R_API_DEBUG_LOGS?: string | undefined;
+ R_SLACK_DEBUG_LOGS?: string | undefined;
}) {
vi.resetModules();
vi.doMock('@roomote/env', async (importOriginal) => {
@@ -22,8 +22,8 @@ async function importLoggingModule(envOverrides?: {
Env: Object.assign(envMock, {
APP_ENV: 'production',
NODE_ENV: 'production',
- API_DEBUG_LOGS: undefined,
- SLACK_DEBUG_LOGS: undefined,
+ R_API_DEBUG_LOGS: undefined,
+ R_SLACK_DEBUG_LOGS: undefined,
...envOverrides,
}),
};
@@ -43,10 +43,10 @@ describe('logging helpers', () => {
vi.restoreAllMocks();
});
- it('enables slackDebug in production when SLACK_DEBUG_LOGS is set', async () => {
+ it('enables slackDebug in production when R_SLACK_DEBUG_LOGS is set', async () => {
const debugSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
const { slackDebug } = await importLoggingModule({
- SLACK_DEBUG_LOGS: '1',
+ R_SLACK_DEBUG_LOGS: '1',
});
slackDebug('[queueSlackMessage] enabled');
@@ -54,10 +54,10 @@ describe('logging helpers', () => {
expect(debugSpy).toHaveBeenCalledWith('[queueSlackMessage] enabled');
});
- it('keeps API_DEBUG_LOGS as a production fallback for shared Slack traces in api processes', async () => {
+ it('keeps R_API_DEBUG_LOGS as a production fallback for shared Slack traces in api processes', async () => {
const debugSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
const { slackDebug } = await importLoggingModule({
- API_DEBUG_LOGS: 'true',
+ R_API_DEBUG_LOGS: 'true',
});
slackDebug('[queueSlackMessage] enabled');
@@ -79,7 +79,7 @@ describe('logging helpers', () => {
const { slackDebug } = await importLoggingModule();
slackDebug('[queueSlackMessage] disabled');
- envMock.SLACK_DEBUG_LOGS = '1';
+ envMock.R_SLACK_DEBUG_LOGS = '1';
slackDebug('[queueSlackMessage] enabled later');
expect(debugSpy).toHaveBeenCalledTimes(1);
diff --git a/packages/slack/src/__tests__/mcp-setup-interruption.test.ts b/packages/slack/src/__tests__/mcp-setup-interruption.test.ts
index 2fe2b751..2a265348 100644
--- a/packages/slack/src/__tests__/mcp-setup-interruption.test.ts
+++ b/packages/slack/src/__tests__/mcp-setup-interruption.test.ts
@@ -69,7 +69,7 @@ vi.mock('@roomote/env', async (importOriginal) => {
...actual,
Env: {
R_APP_URL: 'https://app.example.com',
- TRPC_URL: 'https://api.example.com',
+ R_TRPC_URL: 'https://api.example.com',
},
};
});
diff --git a/packages/slack/src/__tests__/router-debug.test.ts b/packages/slack/src/__tests__/router-debug.test.ts
index f6f3b4f3..1cc5bc22 100644
--- a/packages/slack/src/__tests__/router-debug.test.ts
+++ b/packages/slack/src/__tests__/router-debug.test.ts
@@ -8,7 +8,7 @@ const { chatPostMessageMock, findFirstMock, getDebugChannelMock } = vi.hoisted(
vi.mock('@roomote/env', () => ({
Env: {
- ROUTER_DEBUG_CHANNEL_ID: 'CDEBUG',
+ R_ROUTER_DEBUG_CHANNEL_ID: 'CDEBUG',
},
}));
diff --git a/packages/slack/src/__tests__/show-task-configuration.test.ts b/packages/slack/src/__tests__/show-task-configuration.test.ts
index 035ae54d..3087e65e 100644
--- a/packages/slack/src/__tests__/show-task-configuration.test.ts
+++ b/packages/slack/src/__tests__/show-task-configuration.test.ts
@@ -97,7 +97,7 @@ vi.mock('@roomote/env', async (importOriginal) => {
Env: {
APP_ENV: 'production',
R_APP_URL: 'https://app.example.com',
- TRPC_URL: 'https://api.example.com',
+ R_TRPC_URL: 'https://api.example.com',
},
};
});
diff --git a/packages/slack/src/__tests__/slack-api-base-url.test.ts b/packages/slack/src/__tests__/slack-api-base-url.test.ts
index ac3b7b83..d640ae2a 100644
--- a/packages/slack/src/__tests__/slack-api-base-url.test.ts
+++ b/packages/slack/src/__tests__/slack-api-base-url.test.ts
@@ -1,19 +1,19 @@
import { buildSlackApiUrl, getSlackApiBaseUrl } from '../slack-api-base-url';
describe('slack API base URL helpers', () => {
- const originalBaseUrl = process.env.SLACK_API_BASE_URL;
+ const originalBaseUrl = process.env.R_SLACK_API_BASE_URL;
afterEach(() => {
if (originalBaseUrl === undefined) {
- delete process.env.SLACK_API_BASE_URL;
+ delete process.env.R_SLACK_API_BASE_URL;
return;
}
- process.env.SLACK_API_BASE_URL = originalBaseUrl;
+ process.env.R_SLACK_API_BASE_URL = originalBaseUrl;
});
it('uses the configured base URL and normalizes the trailing slash', () => {
- process.env.SLACK_API_BASE_URL = 'http://127.0.0.1:3012/api';
+ process.env.R_SLACK_API_BASE_URL = 'http://127.0.0.1:3012/api';
expect(getSlackApiBaseUrl()).toBe('http://127.0.0.1:3012/api/');
expect(buildSlackApiUrl('chat.postMessage')).toBe(
@@ -22,7 +22,7 @@ describe('slack API base URL helpers', () => {
});
it('falls back to the default Slack API base URL when process.env is unset', () => {
- delete process.env.SLACK_API_BASE_URL;
+ delete process.env.R_SLACK_API_BASE_URL;
expect(getSlackApiBaseUrl()).toBe('https://slack.com/api/');
});
diff --git a/packages/slack/src/__tests__/slack-notifier.test.ts b/packages/slack/src/__tests__/slack-notifier.test.ts
index fbffa0dc..f7aceba2 100644
--- a/packages/slack/src/__tests__/slack-notifier.test.ts
+++ b/packages/slack/src/__tests__/slack-notifier.test.ts
@@ -32,11 +32,11 @@ vi.mock('@slack/web-api', () => {
describe('SlackNotifier', () => {
const token = 'xoxb-test-token';
- const originalBaseUrl = process.env.SLACK_API_BASE_URL;
+ const originalBaseUrl = process.env.R_SLACK_API_BASE_URL;
let notifier: SlackNotifier;
beforeEach(() => {
- process.env.SLACK_API_BASE_URL = 'https://slack.com/api/';
+ process.env.R_SLACK_API_BASE_URL = 'https://slack.com/api/';
notifier = new SlackNotifier(token);
vi.clearAllMocks();
getGlobalWithFetch().fetch = vi.fn();
@@ -44,11 +44,11 @@ describe('SlackNotifier', () => {
afterEach(() => {
if (originalBaseUrl === undefined) {
- delete process.env.SLACK_API_BASE_URL;
+ delete process.env.R_SLACK_API_BASE_URL;
return;
}
- process.env.SLACK_API_BASE_URL = originalBaseUrl;
+ process.env.R_SLACK_API_BASE_URL = originalBaseUrl;
});
describe('postMessage', () => {
@@ -80,7 +80,7 @@ describe('SlackNotifier', () => {
});
it('suppresses threaded replies when the thread root message is gone', async () => {
- process.env.SLACK_API_BASE_URL = 'http://127.0.0.1:3012/api';
+ process.env.R_SLACK_API_BASE_URL = 'http://127.0.0.1:3012/api';
getGlobalWithFetch().fetch = vi
.fn()
@@ -2853,7 +2853,7 @@ describe('SlackNotifier', () => {
);
expect(WebClientMock).toHaveBeenCalledWith(token, {
slackApiUrl: 'https://slack.com/api/',
- timeout: Env.SLACK_API_TIMEOUT_MS,
+ timeout: Env.R_SLACK_API_TIMEOUT_MS,
});
});
diff --git a/packages/slack/src/__tests__/start-auto-routed-slack-task.test.ts b/packages/slack/src/__tests__/start-auto-routed-slack-task.test.ts
index f3b4a0d3..1585787b 100644
--- a/packages/slack/src/__tests__/start-auto-routed-slack-task.test.ts
+++ b/packages/slack/src/__tests__/start-auto-routed-slack-task.test.ts
@@ -35,7 +35,7 @@ const {
vi.mock('@roomote/env', () => ({
Env: {
R_APP_URL: 'https://app.example.com',
- TRPC_URL: 'https://api.example.com',
+ R_TRPC_URL: 'https://api.example.com',
},
}));
diff --git a/packages/slack/src/__tests__/thread-footer.test.ts b/packages/slack/src/__tests__/thread-footer.test.ts
index 5de79657..573a53f1 100644
--- a/packages/slack/src/__tests__/thread-footer.test.ts
+++ b/packages/slack/src/__tests__/thread-footer.test.ts
@@ -42,8 +42,8 @@ vi.mock('@roomote/db/server', () => ({
vi.mock('@roomote/env', () => ({
Env: {
- PREVIEW_PROXY_BASE_URL: 'https://preview.example.com',
- PREVIEW_DOMAINS: 'preview.example.com',
+ R_PREVIEW_PROXY_BASE_URL: 'https://preview.example.com',
+ R_PREVIEW_DOMAINS: 'preview.example.com',
},
}));
diff --git a/packages/slack/src/__tests__/web-client.test.ts b/packages/slack/src/__tests__/web-client.test.ts
index 0e228118..7db351bb 100644
--- a/packages/slack/src/__tests__/web-client.test.ts
+++ b/packages/slack/src/__tests__/web-client.test.ts
@@ -17,19 +17,19 @@ vi.mock('@slack/web-api', () => ({
import { createSlackWebClient } from '../web-client';
describe('createSlackWebClient', () => {
- const originalBaseUrl = process.env.SLACK_API_BASE_URL;
+ const originalBaseUrl = process.env.R_SLACK_API_BASE_URL;
beforeEach(() => {
- process.env.SLACK_API_BASE_URL = 'https://slack.com/api/';
+ process.env.R_SLACK_API_BASE_URL = 'https://slack.com/api/';
apiCallMock.mockReset();
WebClientMock.mockClear();
});
afterEach(() => {
if (originalBaseUrl === undefined) {
- delete process.env.SLACK_API_BASE_URL;
+ delete process.env.R_SLACK_API_BASE_URL;
} else {
- process.env.SLACK_API_BASE_URL = originalBaseUrl;
+ process.env.R_SLACK_API_BASE_URL = originalBaseUrl;
}
vi.restoreAllMocks();
});
@@ -65,13 +65,13 @@ describe('createSlackWebClient', () => {
operation: 'apiCall(chat.postMessage)',
method: 'POST',
url: 'https://slack.com/api/chat.postMessage',
- timeoutMs: Env.SLACK_API_TIMEOUT_MS,
+ timeoutMs: Env.R_SLACK_API_TIMEOUT_MS,
});
expect(isObservedTimeoutError(caughtError)).toBe(true);
expect(WebClientMock).toHaveBeenCalledWith('xoxb-test', {
slackApiUrl: 'https://slack.com/api/',
- timeout: Env.SLACK_API_TIMEOUT_MS,
+ timeout: Env.R_SLACK_API_TIMEOUT_MS,
});
expect(consoleErrorSpy).toHaveBeenCalledWith(
'[Slack Web API Timeout]',
@@ -80,7 +80,7 @@ describe('createSlackWebClient', () => {
operation: 'apiCall(chat.postMessage)',
method: 'POST',
url: 'https://slack.com/api/chat.postMessage',
- timeoutMs: Env.SLACK_API_TIMEOUT_MS,
+ timeoutMs: Env.R_SLACK_API_TIMEOUT_MS,
}),
);
});
diff --git a/packages/slack/src/block-kit.ts b/packages/slack/src/block-kit.ts
index 98546830..77a8948d 100644
--- a/packages/slack/src/block-kit.ts
+++ b/packages/slack/src/block-kit.ts
@@ -154,7 +154,7 @@ export function buildSlackAccountLinkConnectMessage(
text: copy.buttonText,
emoji: true,
},
- url: `${Env.SLACK_AUTH_URI}?state=${authToken}`,
+ url: `${Env.R_SLACK_AUTH_URI}?state=${authToken}`,
style: 'primary',
},
],
@@ -1145,7 +1145,7 @@ export async function showTaskConfiguration({
...(event.processedVideoDescriptions?.length
? { videoDescriptions: event.processedVideoDescriptions }
: {}),
- apiBaseUrl: Env.TRPC_URL,
+ apiBaseUrl: Env.R_TRPC_URL,
});
console.log(
@@ -2740,7 +2740,7 @@ export async function handleSlackRoutingCorrection({
...(originalEvent.processedVideoDescriptions?.length
? { videoDescriptions: originalEvent.processedVideoDescriptions }
: {}),
- apiBaseUrl: Env.TRPC_URL,
+ apiBaseUrl: Env.R_TRPC_URL,
});
// Add the previous suggestion so the router can revise the workspace.
diff --git a/packages/slack/src/logging.ts b/packages/slack/src/logging.ts
index 1eedf177..ff1e7ec1 100644
--- a/packages/slack/src/logging.ts
+++ b/packages/slack/src/logging.ts
@@ -7,8 +7,8 @@ function isDebugLogsEnabled(value: string | undefined): boolean {
function isSlackDebugLoggingEnabled(): boolean {
const effectiveAppEnv = Env.APP_ENV ?? Env.NODE_ENV;
- const slackDebugLogsEnabled = isDebugLogsEnabled(Env.SLACK_DEBUG_LOGS);
- const apiDebugLogsEnabled = isDebugLogsEnabled(Env.API_DEBUG_LOGS);
+ const slackDebugLogsEnabled = isDebugLogsEnabled(Env.R_SLACK_DEBUG_LOGS);
+ const apiDebugLogsEnabled = isDebugLogsEnabled(Env.R_API_DEBUG_LOGS);
return (
effectiveAppEnv !== 'production' ||
diff --git a/packages/slack/src/slack-api-base-url.ts b/packages/slack/src/slack-api-base-url.ts
index 80a58f7a..511a0090 100644
--- a/packages/slack/src/slack-api-base-url.ts
+++ b/packages/slack/src/slack-api-base-url.ts
@@ -4,8 +4,8 @@ const DEFAULT_SLACK_API_BASE_URL = 'https://slack.com/api/';
export function getSlackApiBaseUrl(): string {
const configuredUrl = (
- process.env.SLACK_API_BASE_URL ??
- Env.SLACK_API_BASE_URL ??
+ process.env.R_SLACK_API_BASE_URL ??
+ Env.R_SLACK_API_BASE_URL ??
DEFAULT_SLACK_API_BASE_URL
).trim();
diff --git a/packages/slack/src/start-auto-routed-slack-task.ts b/packages/slack/src/start-auto-routed-slack-task.ts
index 6fb2275f..3a8c4284 100644
--- a/packages/slack/src/start-auto-routed-slack-task.ts
+++ b/packages/slack/src/start-auto-routed-slack-task.ts
@@ -345,7 +345,7 @@ export async function startAutoRoutedSlackTask({
...(processedVideoDescriptions?.length
? { videoDescriptions: processedVideoDescriptions }
: {}),
- apiBaseUrl: Env.TRPC_URL,
+ apiBaseUrl: Env.R_TRPC_URL,
});
const normalizedRoutingRepositoryConstraint =
routingRepositoryConstraint?.trim().toLowerCase() || null;
diff --git a/packages/slack/src/web-client.ts b/packages/slack/src/web-client.ts
index 855bd469..79c88e08 100644
--- a/packages/slack/src/web-client.ts
+++ b/packages/slack/src/web-client.ts
@@ -57,7 +57,7 @@ function isSlackTimeoutError(error: unknown): error is SlackRequestError {
}
export function createSlackWebClient(token: string): WebClient {
- const timeoutMs = Env.SLACK_API_TIMEOUT_MS;
+ const timeoutMs = Env.R_SLACK_API_TIMEOUT_MS;
const slackApiUrl = getSlackApiBaseUrl();
const client = new WebClient(token, {
diff --git a/packages/types/src/control-plane-env-vars.test.ts b/packages/types/src/control-plane-env-vars.test.ts
index d986a6af..d87b62c7 100644
--- a/packages/types/src/control-plane-env-vars.test.ts
+++ b/packages/types/src/control-plane-env-vars.test.ts
@@ -12,8 +12,8 @@ describe('CONTROL_PLANE_ENV_VAR_NAMES', () => {
'E2B_API_KEY',
'R_GITHUB_APP_PRIVATE_KEY',
'R_GITHUB_WEBHOOK_SECRET',
- 'GITLAB_WEBHOOK_SECRET',
- 'GITLAB_CLIENT_SECRET',
+ 'R_GITLAB_WEBHOOK_SECRET',
+ 'R_GITLAB_CLIENT_SECRET',
'R_MICROSOFT_CLIENT_SECRET',
'R_SLACK_SIGNING_SECRET',
'R_TEAMS_BOT_APP_PASSWORD',
@@ -33,8 +33,8 @@ describe('CONTROL_PLANE_ENV_VAR_NAMES', () => {
for (const name of [
'R_GITHUB_APP_ID',
'R_GITHUB_CLIENT_ID',
- 'GITLAB_CLIENT_ID',
- 'SLACK_APP_ID',
+ 'R_GITLAB_CLIENT_ID',
+ 'R_SLACK_APP_ID',
]) {
expect(CONTROL_PLANE_ENV_VAR_NAMES.has(name)).toBe(true);
}
diff --git a/packages/types/src/control-plane-env-vars.ts b/packages/types/src/control-plane-env-vars.ts
index d08aa477..38e8ba97 100644
--- a/packages/types/src/control-plane-env-vars.ts
+++ b/packages/types/src/control-plane-env-vars.ts
@@ -59,9 +59,11 @@ export const INTEGRATION_BOT_SECRET_ENV_VAR_NAMES: ReadonlySet =
export const PROVIDER_IDENTIFIER_ENV_VAR_NAMES: ReadonlySet = new Set([
'R_GITHUB_APP_ID',
'R_GITHUB_CLIENT_ID',
- 'GITLAB_CLIENT_ID',
- 'GITEA_CLIENT_ID',
- 'SLACK_APP_ID',
+ 'R_GITLAB_CLIENT_ID',
+ 'R_GITEA_CLIENT_ID',
+ 'R_ADO_CLIENT_ID',
+ 'R_BITBUCKET_CLIENT_ID',
+ 'R_SLACK_APP_ID',
]);
/**
diff --git a/packages/types/src/preview-proxy.ts b/packages/types/src/preview-proxy.ts
index 8757c11b..8e5609d0 100644
--- a/packages/types/src/preview-proxy.ts
+++ b/packages/types/src/preview-proxy.ts
@@ -17,13 +17,13 @@ export const DEFAULT_AUTH_BYPASS_HEADER_NAME = 'x-bypass-roomote-auth';
* Environment variable for the preview proxy base URL.
* Contains full protocol, host, and optional port (e.g., 'https://preview.example.com' or 'http://roomotepreview.localhost:18081').
*/
-export const PREVIEW_PROXY_BASE_URL_ENV_VAR = 'PREVIEW_PROXY_BASE_URL';
+export const PREVIEW_PROXY_BASE_URL_ENV_VAR = 'R_PREVIEW_PROXY_BASE_URL';
/**
* Environment variable for the preview domain allowlist.
* Supports comma-separated list, used for redirect_uri validation.
*/
-export const PREVIEW_DOMAIN_ENV_VAR = 'PREVIEW_DOMAINS';
+export const PREVIEW_DOMAIN_ENV_VAR = 'R_PREVIEW_DOMAINS';
/**
* Cookie that disables preview widget injection for a request.
@@ -42,7 +42,7 @@ export const HIDE_PREVIEW_WIDGET_COOKIE = 'roomote_hide_preview_widget';
export function getPreviewProxyBaseUrl(baseUrlRaw: string | undefined): string {
if (!baseUrlRaw) {
throw new Error(
- 'PREVIEW_PROXY_BASE_URL environment variable is required but not configured',
+ 'R_PREVIEW_PROXY_BASE_URL environment variable is required but not configured',
);
}
@@ -50,7 +50,9 @@ export function getPreviewProxyBaseUrl(baseUrlRaw: string | undefined): string {
try {
new URL(baseUrlRaw);
} catch {
- throw new Error(`PREVIEW_PROXY_BASE_URL is not a valid URL: ${baseUrlRaw}`);
+ throw new Error(
+ `R_PREVIEW_PROXY_BASE_URL is not a valid URL: ${baseUrlRaw}`,
+ );
}
return baseUrlRaw;
@@ -116,7 +118,7 @@ export function getPreviewDomain(
): string {
if (!previewDomainsRaw) {
throw new Error(
- 'PREVIEW_DOMAINS environment variable is required but not configured',
+ 'R_PREVIEW_DOMAINS environment variable is required but not configured',
);
}
@@ -127,7 +129,9 @@ export function getPreviewDomain(
const domain = domains[0];
if (!domain) {
- throw new Error('PREVIEW_DOMAINS environment variable is empty or invalid');
+ throw new Error(
+ 'R_PREVIEW_DOMAINS environment variable is empty or invalid',
+ );
}
return domain;
diff --git a/packages/types/src/setup-auth-config.test.ts b/packages/types/src/setup-auth-config.test.ts
index 4cb47a4a..24328613 100644
--- a/packages/types/src/setup-auth-config.test.ts
+++ b/packages/types/src/setup-auth-config.test.ts
@@ -314,7 +314,7 @@ describe('buildSetupAuthStatus', () => {
const slack = status.providers.find((provider) => provider.id === 'slack');
const slackAppIdField = slack?.fields.find(
- (field) => field.envVarName === 'SLACK_APP_ID',
+ (field) => field.envVarName === 'R_SLACK_APP_ID',
);
expect(slack).toMatchObject({
diff --git a/packages/types/src/setup-compute-config.test.ts b/packages/types/src/setup-compute-config.test.ts
index 830758d6..f469adee 100644
--- a/packages/types/src/setup-compute-config.test.ts
+++ b/packages/types/src/setup-compute-config.test.ts
@@ -40,7 +40,7 @@ describe('normalizeDeploymentComputeConfig', () => {
describe('buildSetupComputeStatus', () => {
it('is unsatisfied without an explicit provider choice', () => {
const status = buildSetupComputeStatus({
- runtimeEnv: { DEFAULT_COMPUTE_PROVIDER: 'docker' },
+ runtimeEnv: { R_DEFAULT_COMPUTE_PROVIDER: 'docker' },
});
expect(status.selectedProvider).toBeNull();
@@ -131,12 +131,12 @@ describe('buildSetupComputeStatus', () => {
expect(infrastructureByProvider.blaxel).toEqual([
'BLAXEL_IMAGE',
'BLAXEL_REGION',
- 'BLAXEL_STANDBY_MAX_COUNT',
- 'BLAXEL_STANDBY_MAX_AGE_HOURS',
+ 'R_BLAXEL_STANDBY_MAX_COUNT',
+ 'R_BLAXEL_STANDBY_MAX_AGE_HOURS',
]);
expect(infrastructureByProvider.docker).toEqual([
- 'DOCKER_STANDBY_MAX_COUNT',
- 'DOCKER_STANDBY_MAX_AGE_HOURS',
+ 'R_DOCKER_STANDBY_MAX_COUNT',
+ 'R_DOCKER_STANDBY_MAX_AGE_HOURS',
]);
// Advanced infrastructure fields are surfaced behind an advanced area.
@@ -227,7 +227,7 @@ describe('buildSetupComputeStatus', () => {
it('reports the shared worker image status', () => {
const notReady = buildSetupComputeStatus({});
expect(notReady.workerImage).toMatchObject({
- envVarName: 'DOCKER_WORKER_IMAGE',
+ envVarName: 'R_DOCKER_WORKER_IMAGE',
runtimeSatisfied: false,
savedSatisfied: false,
hostedImageRef: null,
@@ -236,7 +236,7 @@ describe('buildSetupComputeStatus', () => {
const runtimeReady = buildSetupComputeStatus({
runtimeEnv: {
- DOCKER_WORKER_IMAGE: 'ghcr.io/roocodeinc/roomote-worker:v1.2.3',
+ R_DOCKER_WORKER_IMAGE: 'ghcr.io/roocodeinc/roomote-worker:v1.2.3',
},
});
expect(runtimeReady.workerImage).toMatchObject({
@@ -248,7 +248,7 @@ describe('buildSetupComputeStatus', () => {
it('does not treat a local worker tag as hosted-ready', () => {
const status = buildSetupComputeStatus({
- runtimeEnv: { DOCKER_WORKER_IMAGE: 'roomote-worker:local' },
+ runtimeEnv: { R_DOCKER_WORKER_IMAGE: 'roomote-worker:local' },
});
expect(status.workerImage.runtimeSatisfied).toBe(true);
@@ -258,7 +258,7 @@ describe('buildSetupComputeStatus', () => {
it('ignores a registry-qualified saved worker image for hosted readiness', () => {
const status = buildSetupComputeStatus({
- persistedEnvVarNames: ['DOCKER_WORKER_IMAGE'],
+ persistedEnvVarNames: ['R_DOCKER_WORKER_IMAGE'],
savedWorkerImage: 'ghcr.io/roocodeinc/roomote-worker:v9.9.9',
});
@@ -269,7 +269,7 @@ describe('buildSetupComputeStatus', () => {
hostedReady: false,
});
- // Legacy saved DOCKER_WORKER_IMAGE rows no longer satisfy hosted readiness.
+ // Legacy saved R_DOCKER_WORKER_IMAGE rows no longer satisfy hosted readiness.
// Release derivation / process env must provide a registry-qualified image.
const modal = status.providers.find(
(provider) => provider.provider === 'modal',
@@ -331,7 +331,7 @@ describe('buildSetupComputeStatus', () => {
const provisionable = buildSetupComputeStatus({
runtimeEnv: {
- DOCKER_WORKER_IMAGE: 'ghcr.io/roocodeinc/roomote-worker:v1.2.3',
+ R_DOCKER_WORKER_IMAGE: 'ghcr.io/roocodeinc/roomote-worker:v1.2.3',
},
});
@@ -351,7 +351,7 @@ describe('buildSetupComputeStatus', () => {
expect(e2bStatus?.configSatisfied).toBe(false);
const localImage = buildSetupComputeStatus({
- runtimeEnv: { DOCKER_WORKER_IMAGE: 'roomote-worker:local' },
+ runtimeEnv: { R_DOCKER_WORKER_IMAGE: 'roomote-worker:local' },
});
expect(findTemplateField(localImage)?.setupProvisionable).toBe(false);
@@ -367,7 +367,7 @@ describe('buildSetupComputeStatus', () => {
const provisionable = buildSetupComputeStatus({
runtimeEnv: {
- DOCKER_WORKER_IMAGE: 'ghcr.io/roocodeinc/roomote-worker:v1.2.3',
+ R_DOCKER_WORKER_IMAGE: 'ghcr.io/roocodeinc/roomote-worker:v1.2.3',
},
});
@@ -380,7 +380,7 @@ describe('buildSetupComputeStatus', () => {
expect(daytonaStatus?.configSatisfied).toBe(false);
const localImage = buildSetupComputeStatus({
- runtimeEnv: { DOCKER_WORKER_IMAGE: 'roomote-worker:local' },
+ runtimeEnv: { R_DOCKER_WORKER_IMAGE: 'roomote-worker:local' },
});
expect(findSnapshotField(localImage)?.setupProvisionable).toBe(false);
@@ -388,7 +388,7 @@ describe('buildSetupComputeStatus', () => {
it('prefers the wizard selection over the persisted default for preselection', () => {
const status = buildSetupComputeStatus({
- runtimeEnv: { DEFAULT_COMPUTE_PROVIDER: 'docker' },
+ runtimeEnv: { R_DEFAULT_COMPUTE_PROVIDER: 'docker' },
persistedComputeConfig: { defaultProvider: 'modal' },
selectedProvider: 'e2b',
});
@@ -401,8 +401,8 @@ describe('buildSetupComputeStatus', () => {
it('skips excluded runtime defaults when preselecting a provider', () => {
const status = buildSetupComputeStatus({
runtimeEnv: {
- DEFAULT_COMPUTE_PROVIDER: 'docker',
- EXCLUDED_COMPUTE_PROVIDERS: 'docker',
+ R_DEFAULT_COMPUTE_PROVIDER: 'docker',
+ R_EXCLUDED_COMPUTE_PROVIDERS: 'docker',
MODAL_TOKEN_ID: 'id',
MODAL_TOKEN_SECRET: 'secret',
MODAL_BASE_IMAGE_REF: 'registry/image:tag',
@@ -418,7 +418,7 @@ describe('buildSetupComputeStatus', () => {
runtimeEnv: {
MODAL_TOKEN_ID: 'id',
MODAL_TOKEN_SECRET: 'secret',
- DOCKER_WORKER_IMAGE: 'ghcr.io/roocodeinc/roomote-worker:v1.2.3',
+ R_DOCKER_WORKER_IMAGE: 'ghcr.io/roocodeinc/roomote-worker:v1.2.3',
},
persistedComputeConfig: { defaultProvider: 'modal' },
});
@@ -494,7 +494,7 @@ describe('buildSetupComputeStatus', () => {
runtimeEnv: {
MODAL_TOKEN_ID: 'id',
MODAL_TOKEN_SECRET: 'secret',
- DOCKER_WORKER_IMAGE: 'roomote-worker:local',
+ R_DOCKER_WORKER_IMAGE: 'roomote-worker:local',
},
persistedComputeConfig: { defaultProvider: 'modal' },
});
@@ -516,7 +516,7 @@ describe('buildSetupComputeStatus', () => {
NODE_ENV: 'development',
MODAL_TOKEN_ID: 'id',
MODAL_TOKEN_SECRET: 'secret',
- DOCKER_WORKER_IMAGE: 'roomote-worker:local',
+ R_DOCKER_WORKER_IMAGE: 'roomote-worker:local',
},
persistedComputeConfig: { defaultProvider: 'modal' },
});
@@ -540,7 +540,7 @@ describe('buildSetupComputeStatus', () => {
it('reports provider infrastructure availability for the picker', () => {
const withWorkerImage = buildSetupComputeStatus({
runtimeEnv: {
- DOCKER_WORKER_IMAGE: 'ghcr.io/roocodeinc/roomote-worker:v1.2.3',
+ R_DOCKER_WORKER_IMAGE: 'ghcr.io/roocodeinc/roomote-worker:v1.2.3',
},
});
@@ -618,7 +618,7 @@ describe('buildSetupComputeStatus', () => {
describe('getComputeFieldValidationError', () => {
const field = {
- envVarName: 'BLAXEL_STANDBY_MAX_AGE_HOURS',
+ envVarName: 'R_BLAXEL_STANDBY_MAX_AGE_HOURS',
label: 'Retention period (hours)',
required: false,
category: 'infrastructure' as const,
@@ -695,10 +695,10 @@ describe('deriveWorkerImageFromReleaseVersion', () => {
});
describe('resolveEffectiveDockerWorkerImage', () => {
- it('prefers an explicit DOCKER_WORKER_IMAGE over the derived default', () => {
+ it('prefers an explicit R_DOCKER_WORKER_IMAGE over the derived default', () => {
expect(
resolveEffectiveDockerWorkerImage({
- DOCKER_WORKER_IMAGE: 'registry.example.com/custom/worker:pinned',
+ R_DOCKER_WORKER_IMAGE: 'registry.example.com/custom/worker:pinned',
RELEASE_VERSION: 'v1.2.3',
}),
).toBe('registry.example.com/custom/worker:pinned');
@@ -710,7 +710,7 @@ describe('resolveEffectiveDockerWorkerImage', () => {
).toBe('ghcr.io/roocodeinc/roomote-worker:v1.2.3');
expect(
resolveEffectiveDockerWorkerImage({
- DOCKER_WORKER_IMAGE: ' ',
+ R_DOCKER_WORKER_IMAGE: ' ',
RELEASE_VERSION: 'v1.2.3',
}),
).toBe('ghcr.io/roocodeinc/roomote-worker:v1.2.3');
@@ -748,7 +748,7 @@ describe('resolveDerivedModalBaseImageRef', () => {
);
expect(
resolveDerivedModalBaseImageRef({
- DOCKER_WORKER_IMAGE: 'registry.example.com/custom/worker:tag',
+ R_DOCKER_WORKER_IMAGE: 'registry.example.com/custom/worker:tag',
RELEASE_VERSION: 'v1.2.3',
}),
).toBe('registry.example.com/custom/worker:tag');
@@ -761,7 +761,7 @@ describe('resolveDerivedModalBaseImageRef', () => {
expect(
resolveDerivedModalBaseImageRef({
APP_ENV: 'development',
- DOCKER_WORKER_IMAGE: 'roomote-worker:local',
+ R_DOCKER_WORKER_IMAGE: 'roomote-worker:local',
}),
).toBe(DEVELOPMENT_MODAL_BASE_IMAGE_REF);
});
diff --git a/packages/types/src/setup-compute-config.ts b/packages/types/src/setup-compute-config.ts
index 5b4e7b45..128ecb1f 100644
--- a/packages/types/src/setup-compute-config.ts
+++ b/packages/types/src/setup-compute-config.ts
@@ -137,7 +137,7 @@ export type SetupComputeProviderStatus = Omit<
};
/**
- * Status of the shared hosted-compute worker image (`DOCKER_WORKER_IMAGE`).
+ * Status of the shared hosted-compute worker image (`R_DOCKER_WORKER_IMAGE`).
* Hosted providers (Modal, E2B, Daytona) derive or provision their worker
* base image from this value, so it is surfaced once, above the provider
* sections, rather than per provider.
@@ -145,7 +145,7 @@ export type SetupComputeProviderStatus = Omit<
export type SetupComputeWorkerImageStatus = {
envVarName: typeof SHARED_WORKER_IMAGE_ENV_VAR;
label: string;
- /** Satisfied by the process env (`DOCKER_WORKER_IMAGE`), which locks the UI field. */
+ /** Satisfied by the process env (`R_DOCKER_WORKER_IMAGE`), which locks the UI field. */
runtimeSatisfied: boolean;
/** Satisfied by a saved deployment env var. */
savedSatisfied: boolean;
@@ -184,7 +184,7 @@ export const DEFAULT_LOCAL_DOCKER_WORKER_IMAGE = 'roomote-worker:local';
* provision their worker base image from this value, so it is configured
* once for the whole deployment rather than per provider.
*/
-export const SHARED_WORKER_IMAGE_ENV_VAR = 'DOCKER_WORKER_IMAGE';
+export const SHARED_WORKER_IMAGE_ENV_VAR = 'R_DOCKER_WORKER_IMAGE';
export function parseExcludedComputeProviders(
value: string | null | undefined,
@@ -357,7 +357,7 @@ export const SETUP_COMPUTE_PROVIDER_CATALOG = [
advanced: true,
},
{
- envVarName: 'BLAXEL_STANDBY_MAX_COUNT',
+ envVarName: 'R_BLAXEL_STANDBY_MAX_COUNT',
label: 'Maximum retained tasks',
required: false,
category: 'infrastructure',
@@ -366,7 +366,7 @@ export const SETUP_COMPUTE_PROVIDER_CATALOG = [
helpText: 'Defaults to 25. Set to 0 to disable standby retention.',
},
{
- envVarName: 'BLAXEL_STANDBY_MAX_AGE_HOURS',
+ envVarName: 'R_BLAXEL_STANDBY_MAX_AGE_HOURS',
label: 'Retention period (hours)',
required: false,
category: 'infrastructure',
@@ -392,7 +392,7 @@ export const SETUP_COMPUTE_PROVIDER_CATALOG = [
supportsSnapshots: false,
fields: [
{
- envVarName: 'DOCKER_STANDBY_MAX_COUNT',
+ envVarName: 'R_DOCKER_STANDBY_MAX_COUNT',
label: 'Maximum retained tasks',
required: false,
category: 'infrastructure',
@@ -401,7 +401,7 @@ export const SETUP_COMPUTE_PROVIDER_CATALOG = [
helpText: 'Defaults to 10. Set to 0 to disable standby retention.',
},
{
- envVarName: 'DOCKER_STANDBY_MAX_AGE_HOURS',
+ envVarName: 'R_DOCKER_STANDBY_MAX_AGE_HOURS',
label: 'Retention period (hours)',
required: false,
category: 'infrastructure',
@@ -475,7 +475,7 @@ export function isAutoProvisionedComputeArtifactField(
/**
* True for managed Modal / E2B / Daytona / Blaxel worker-image artifact env vars that
* Settings and setup never collect as form inputs. Process env, derivation
- * from DOCKER_WORKER_IMAGE / RELEASE_VERSION, or detached provisioning owns
+ * from R_DOCKER_WORKER_IMAGE / RELEASE_VERSION, or detached provisioning owns
* them.
*/
export function isManagedComputeArtifactField(
@@ -586,14 +586,14 @@ export function deriveWorkerImageFromReleaseVersion(
/**
* The worker image the deployment effectively uses: an explicit
- * DOCKER_WORKER_IMAGE always wins; otherwise the ref derived from the baked
+ * R_DOCKER_WORKER_IMAGE always wins; otherwise the ref derived from the baked
* RELEASE_VERSION. Null when neither is available (local dev, where the
* bare local-worker fallback applies at the env-loader layer).
*/
export function resolveEffectiveDockerWorkerImage(
runtimeEnv: Partial>,
): string | null {
- const explicit = runtimeEnv.DOCKER_WORKER_IMAGE?.trim();
+ const explicit = runtimeEnv.R_DOCKER_WORKER_IMAGE?.trim();
if (explicit) {
return explicit;
@@ -675,7 +675,7 @@ export function buildSetupComputeStatus(input: {
persistedComputeConfig?: Partial | null;
selectedProvider?: ComputeProvider | null;
/**
- * Raw saved deployment `DOCKER_WORKER_IMAGE` value, when the caller has
+ * Raw saved deployment `R_DOCKER_WORKER_IMAGE` value, when the caller has
* resolved it. Process env still wins; this lets a worker image saved
* through the UI count toward hosted readiness before the process restarts.
*/
@@ -690,9 +690,9 @@ export function buildSetupComputeStatus(input: {
input.persistedComputeConfig,
);
- const runtimeDefaultValue = runtimeEnv.DEFAULT_COMPUTE_PROVIDER?.trim();
+ const runtimeDefaultValue = runtimeEnv.R_DEFAULT_COMPUTE_PROVIDER?.trim();
const excludedProviders = parseExcludedComputeProviders(
- runtimeEnv.EXCLUDED_COMPUTE_PROVIDERS,
+ runtimeEnv.R_EXCLUDED_COMPUTE_PROVIDERS,
);
const runtimeDefaultProvider =
runtimeDefaultValue &&
@@ -703,11 +703,11 @@ export function buildSetupComputeStatus(input: {
// Worker image resolution is deploy/runtime-managed only: process env wins,
// then the ref derived from the baked RELEASE_VERSION. Legacy saved
- // deployment DOCKER_WORKER_IMAGE rows (from the removed Settings section)
+ // deployment R_DOCKER_WORKER_IMAGE rows (from the removed Settings section)
// are ignored so they cannot stick above release-derived images. Only a
// registry-qualified ref is hosted-ready; a bare local tag is not pullable
// by hosted providers.
- const explicitWorkerImage = runtimeEnv.DOCKER_WORKER_IMAGE?.trim() || null;
+ const explicitWorkerImage = runtimeEnv.R_DOCKER_WORKER_IMAGE?.trim() || null;
const effectiveWorkerImage =
explicitWorkerImage ?? deriveWorkerImageFromReleaseVersion(runtimeEnv);
const hostedWorkerImageRef =
@@ -720,7 +720,7 @@ export function buildSetupComputeStatus(input: {
const workerImage: SetupComputeWorkerImageStatus = {
envVarName: SHARED_WORKER_IMAGE_ENV_VAR,
label: 'Worker image',
- runtimeSatisfied: isConfiguredEnvValue(runtimeEnv.DOCKER_WORKER_IMAGE),
+ runtimeSatisfied: isConfiguredEnvValue(runtimeEnv.R_DOCKER_WORKER_IMAGE),
// Legacy DB-backed worker images are no longer part of readiness/status.
savedSatisfied: false,
hostedImageRef: hostedWorkerImageRef,
diff --git a/packages/types/src/setup-source-control-config.test.ts b/packages/types/src/setup-source-control-config.test.ts
index f0cb8e1d..d0a41c31 100644
--- a/packages/types/src/setup-source-control-config.test.ts
+++ b/packages/types/src/setup-source-control-config.test.ts
@@ -116,7 +116,7 @@ describe('buildSetupSourceControlStatus', () => {
it('preselects the runtime-configured provider when nothing is connected', () => {
const status = buildSetupSourceControlStatus({
runtimeEnv: {
- GITEA_BASE_URL: 'https://gitea.example.com',
+ R_GITEA_BASE_URL: 'https://gitea.example.com',
GITEA_TOKEN: 'gitea-token',
},
});
@@ -178,7 +178,7 @@ describe('buildSetupSourceControlStatus', () => {
it('does not require optional fields to satisfy config', () => {
const status = buildSetupSourceControlStatus({
runtimeEnv: {
- ADO_ORGANIZATION: 'my-org',
+ R_ADO_ORGANIZATION: 'my-org',
ADO_TOKEN: 'ado-token',
},
});
@@ -186,19 +186,19 @@ describe('buildSetupSourceControlStatus', () => {
const ado = status.providers.find((p) => p.provider === 'ado');
expect(ado).toMatchObject({ configSatisfied: true });
const optionalBaseUrl = ado?.fields.find(
- (f) => f.envVarName === 'ADO_BASE_URL',
+ (f) => f.envVarName === 'R_ADO_BASE_URL',
);
const optionalAdoWebhookSecret = ado?.fields.find(
- (f) => f.envVarName === 'ADO_WEBHOOK_SECRET',
+ (f) => f.envVarName === 'R_ADO_WEBHOOK_SECRET',
);
const optionalAdoClientId = ado?.fields.find(
- (f) => f.envVarName === 'ADO_CLIENT_ID',
+ (f) => f.envVarName === 'R_ADO_CLIENT_ID',
);
const optionalAdoClientSecret = ado?.fields.find(
- (f) => f.envVarName === 'ADO_CLIENT_SECRET',
+ (f) => f.envVarName === 'R_ADO_CLIENT_SECRET',
);
const optionalAdoTenantId = ado?.fields.find(
- (f) => f.envVarName === 'ADO_TENANT_ID',
+ (f) => f.envVarName === 'R_ADO_TENANT_ID',
);
expect(optionalBaseUrl).toMatchObject({
required: false,
@@ -235,20 +235,20 @@ describe('buildSetupSourceControlStatus', () => {
});
const gitlab = gitlabStatus.providers.find((p) => p.provider === 'gitlab');
const optionalGitLabClientId = gitlab?.fields.find(
- (field) => field.envVarName === 'GITLAB_CLIENT_ID',
+ (field) => field.envVarName === 'R_GITLAB_CLIENT_ID',
);
const optionalGitLabClientSecret = gitlab?.fields.find(
- (field) => field.envVarName === 'GITLAB_CLIENT_SECRET',
+ (field) => field.envVarName === 'R_GITLAB_CLIENT_SECRET',
);
const giteaStatus = buildSetupSourceControlStatus({
runtimeEnv: {
- GITEA_BASE_URL: 'https://gitea.example.com',
+ R_GITEA_BASE_URL: 'https://gitea.example.com',
GITEA_TOKEN: 'gitea-token',
},
});
const gitea = giteaStatus.providers.find((p) => p.provider === 'gitea');
const optionalGiteaWebhookSecret = gitea?.fields.find(
- (field) => field.envVarName === 'GITEA_WEBHOOK_SECRET',
+ (field) => field.envVarName === 'R_GITEA_WEBHOOK_SECRET',
);
expect(optionalGitLabClientId).toMatchObject({
@@ -270,7 +270,7 @@ describe('buildSetupSourceControlStatus', () => {
it('marks config unsatisfied when a required field is missing', () => {
const status = buildSetupSourceControlStatus({
- runtimeEnv: { ADO_ORGANIZATION: 'my-org' },
+ runtimeEnv: { R_ADO_ORGANIZATION: 'my-org' },
});
const ado = status.providers.find((p) => p.provider === 'ado');
diff --git a/packages/types/src/setup-source-control-config.ts b/packages/types/src/setup-source-control-config.ts
index fff09789..3c96e201 100644
--- a/packages/types/src/setup-source-control-config.ts
+++ b/packages/types/src/setup-source-control-config.ts
@@ -181,34 +181,34 @@ function buildProviderFields(
secret: true,
},
{
- envVarName: 'GITLAB_BASE_URL',
- acceptedEnvVarNames: ['GITLAB_BASE_URL'],
+ envVarName: 'R_GITLAB_BASE_URL',
+ acceptedEnvVarNames: ['R_GITLAB_BASE_URL'],
label: 'GitLab Base URL',
required: false,
},
{
- envVarName: 'GITLAB_CLIENT_ID',
- acceptedEnvVarNames: ['GITLAB_CLIENT_ID'],
+ envVarName: 'R_GITLAB_CLIENT_ID',
+ acceptedEnvVarNames: ['R_GITLAB_CLIENT_ID'],
label: 'GitLab OAuth Client ID',
required: false,
},
{
- envVarName: 'GITLAB_CLIENT_SECRET',
- acceptedEnvVarNames: ['GITLAB_CLIENT_SECRET'],
+ envVarName: 'R_GITLAB_CLIENT_SECRET',
+ acceptedEnvVarNames: ['R_GITLAB_CLIENT_SECRET'],
label: 'GitLab OAuth Client Secret',
secret: true,
required: false,
},
{
- envVarName: 'GITLAB_WEBHOOK_SIGNING_TOKEN',
- acceptedEnvVarNames: ['GITLAB_WEBHOOK_SIGNING_TOKEN'],
+ envVarName: 'R_GITLAB_WEBHOOK_SIGNING_TOKEN',
+ acceptedEnvVarNames: ['R_GITLAB_WEBHOOK_SIGNING_TOKEN'],
label: 'GitLab Webhook Signing Token',
secret: true,
required: false,
},
{
- envVarName: 'GITLAB_WEBHOOK_SECRET',
- acceptedEnvVarNames: ['GITLAB_WEBHOOK_SECRET'],
+ envVarName: 'R_GITLAB_WEBHOOK_SECRET',
+ acceptedEnvVarNames: ['R_GITLAB_WEBHOOK_SECRET'],
label: 'GitLab Webhook Secret',
secret: true,
required: false,
@@ -217,8 +217,8 @@ function buildProviderFields(
case 'gitea':
return [
{
- envVarName: 'GITEA_BASE_URL',
- acceptedEnvVarNames: ['GITEA_BASE_URL'],
+ envVarName: 'R_GITEA_BASE_URL',
+ acceptedEnvVarNames: ['R_GITEA_BASE_URL'],
label: 'Gitea Base URL',
},
{
@@ -228,27 +228,27 @@ function buildProviderFields(
secret: true,
},
{
- envVarName: 'GITEA_USERNAME',
- acceptedEnvVarNames: ['GITEA_USERNAME'],
+ envVarName: 'R_GITEA_USERNAME',
+ acceptedEnvVarNames: ['R_GITEA_USERNAME'],
label: 'Gitea Username',
required: false,
},
{
- envVarName: 'GITEA_CLIENT_ID',
- acceptedEnvVarNames: ['GITEA_CLIENT_ID'],
+ envVarName: 'R_GITEA_CLIENT_ID',
+ acceptedEnvVarNames: ['R_GITEA_CLIENT_ID'],
label: 'Gitea OAuth Client ID',
required: false,
},
{
- envVarName: 'GITEA_CLIENT_SECRET',
- acceptedEnvVarNames: ['GITEA_CLIENT_SECRET'],
+ envVarName: 'R_GITEA_CLIENT_SECRET',
+ acceptedEnvVarNames: ['R_GITEA_CLIENT_SECRET'],
label: 'Gitea OAuth Client Secret',
secret: true,
required: false,
},
{
- envVarName: 'GITEA_WEBHOOK_SECRET',
- acceptedEnvVarNames: ['GITEA_WEBHOOK_SECRET'],
+ envVarName: 'R_GITEA_WEBHOOK_SECRET',
+ acceptedEnvVarNames: ['R_GITEA_WEBHOOK_SECRET'],
label: 'Gitea Webhook Secret',
secret: true,
required: false,
@@ -257,8 +257,8 @@ function buildProviderFields(
case 'ado':
return [
{
- envVarName: 'ADO_ORGANIZATION',
- acceptedEnvVarNames: ['ADO_ORGANIZATION'],
+ envVarName: 'R_ADO_ORGANIZATION',
+ acceptedEnvVarNames: ['R_ADO_ORGANIZATION'],
label: 'Azure DevOps Organization',
},
{
@@ -268,39 +268,39 @@ function buildProviderFields(
secret: true,
},
{
- envVarName: 'ADO_BASE_URL',
- acceptedEnvVarNames: ['ADO_BASE_URL'],
+ envVarName: 'R_ADO_BASE_URL',
+ acceptedEnvVarNames: ['R_ADO_BASE_URL'],
label: 'Azure DevOps Base URL',
required: false,
},
{
- envVarName: 'ADO_USERNAME',
- acceptedEnvVarNames: ['ADO_USERNAME'],
+ envVarName: 'R_ADO_USERNAME',
+ acceptedEnvVarNames: ['R_ADO_USERNAME'],
label: 'Azure DevOps Username',
required: false,
},
{
- envVarName: 'ADO_CLIENT_ID',
- acceptedEnvVarNames: ['ADO_CLIENT_ID'],
+ envVarName: 'R_ADO_CLIENT_ID',
+ acceptedEnvVarNames: ['R_ADO_CLIENT_ID'],
label: 'Microsoft Entra Client ID',
required: false,
},
{
- envVarName: 'ADO_CLIENT_SECRET',
- acceptedEnvVarNames: ['ADO_CLIENT_SECRET'],
+ envVarName: 'R_ADO_CLIENT_SECRET',
+ acceptedEnvVarNames: ['R_ADO_CLIENT_SECRET'],
label: 'Microsoft Entra Client Secret',
secret: true,
required: false,
},
{
- envVarName: 'ADO_TENANT_ID',
- acceptedEnvVarNames: ['ADO_TENANT_ID'],
+ envVarName: 'R_ADO_TENANT_ID',
+ acceptedEnvVarNames: ['R_ADO_TENANT_ID'],
label: 'Microsoft Entra Tenant ID',
required: false,
},
{
- envVarName: 'ADO_WEBHOOK_SECRET',
- acceptedEnvVarNames: ['ADO_WEBHOOK_SECRET'],
+ envVarName: 'R_ADO_WEBHOOK_SECRET',
+ acceptedEnvVarNames: ['R_ADO_WEBHOOK_SECRET'],
label: 'Azure DevOps Webhook Secret',
secret: true,
required: false,
@@ -315,32 +315,32 @@ function buildProviderFields(
secret: true,
},
{
- envVarName: 'BITBUCKET_USERNAME',
- acceptedEnvVarNames: ['BITBUCKET_USERNAME'],
+ envVarName: 'R_BITBUCKET_USERNAME',
+ acceptedEnvVarNames: ['R_BITBUCKET_USERNAME'],
label: 'Atlassian Account Email',
},
{
- envVarName: 'BITBUCKET_BASE_URL',
- acceptedEnvVarNames: ['BITBUCKET_BASE_URL'],
+ envVarName: 'R_BITBUCKET_BASE_URL',
+ acceptedEnvVarNames: ['R_BITBUCKET_BASE_URL'],
label: 'Bitbucket Base URL',
required: false,
},
{
- envVarName: 'BITBUCKET_CLIENT_ID',
- acceptedEnvVarNames: ['BITBUCKET_CLIENT_ID'],
+ envVarName: 'R_BITBUCKET_CLIENT_ID',
+ acceptedEnvVarNames: ['R_BITBUCKET_CLIENT_ID'],
label: 'Bitbucket OAuth Client ID',
required: false,
},
{
- envVarName: 'BITBUCKET_CLIENT_SECRET',
- acceptedEnvVarNames: ['BITBUCKET_CLIENT_SECRET'],
+ envVarName: 'R_BITBUCKET_CLIENT_SECRET',
+ acceptedEnvVarNames: ['R_BITBUCKET_CLIENT_SECRET'],
label: 'Bitbucket OAuth Client Secret',
secret: true,
required: false,
},
{
- envVarName: 'BITBUCKET_WEBHOOK_SECRET',
- acceptedEnvVarNames: ['BITBUCKET_WEBHOOK_SECRET'],
+ envVarName: 'R_BITBUCKET_WEBHOOK_SECRET',
+ acceptedEnvVarNames: ['R_BITBUCKET_WEBHOOK_SECRET'],
label: 'Bitbucket Webhook Secret',
secret: true,
required: false,
diff --git a/render.yaml b/render.yaml
index 81354667..1cb06e3b 100644
--- a/render.yaml
+++ b/render.yaml
@@ -22,7 +22,7 @@
# service receives host-only references (`ROOMOTE_WEB_HOST`,
# `ROOMOTE_API_HOST`, `ROOMOTE_MINIO_HOST`, `ROOMOTE_MINIO_HOSTPORT`) and
# the entrypoint dispatcher composes the URL-shaped variables
-# (`R_APP_URL`, `TRPC_URL`, `S3_ENDPOINT`, `S3_PRESIGN_ENDPOINT`)
+# (`R_APP_URL`, `R_TRPC_URL`, `S3_ENDPOINT`, `S3_PRESIGN_ENDPOINT`)
# from them when they are unset. The cross-service references use
# Render's `fromService` + `envVarKey` form against the Render-provided
# `RENDER_EXTERNAL_HOSTNAME`; the first-boot verification in
@@ -38,17 +38,17 @@
# `S3_SECRET_ACCESS_KEY` with a `fromService` reference.
# - Render has no Docker socket, so task execution must use a hosted
# sandbox provider (modal by default; e2b and daytona also work).
-# EXCLUDED_COMPUTE_PROVIDERS=docker hides the unusable provider.
+# R_EXCLUDED_COMPUTE_PROVIDERS=docker hides the unusable provider.
# - Zero deploy-time inputs. Modal tokens and model provider keys are
# entered in the /setup wizard after first boot and stored encrypted in
# Postgres. Auth keypairs come from R_AUTO_GENERATE_KEYS=true.
# - The api service must be publicly reachable: hosted-sandbox workers and
-# GitHub webhooks call `TRPC_URL` directly. MinIO is public too because
+# GitHub webhooks call `R_TRPC_URL` directly. MinIO is public too because
# presigned artifact URLs (`S3_PRESIGN_ENDPOINT`) must be reachable from
# workers and browsers; Render routes its HTTPS domain to port 9000.
# - The Blueprint tracks the mutable `:main` image alias (stable main
# branch builds) and contains no version strings. The images bake
-# RELEASE_VERSION; the app derives DOCKER_WORKER_IMAGE and
+# RELEASE_VERSION; the app derives R_DOCKER_WORKER_IMAGE and
# MODAL_BASE_IMAGE_REF from it, and the controller reads the worker
# release version from the VERSION file inside worker-current.tar.gz.
# Production deployments can pin an immutable `v*` or `main-` tag
@@ -226,11 +226,11 @@ envVarGroups:
value: roomote-artifacts
- key: S3_AUTO_CREATE_BUCKET
value: 'true' # api creates the MinIO bucket at boot; no manual mc step
- - key: DEFAULT_COMPUTE_PROVIDER
+ - key: R_DEFAULT_COMPUTE_PROVIDER
value: modal
- - key: EXCLUDED_COMPUTE_PROVIDERS
+ - key: R_EXCLUDED_COMPUTE_PROVIDERS
value: docker
# Only the controller reads this, but the value is a constant baked
# into the app image, so the shared group is the simplest home for it.
- - key: DOCKER_WORKER_RELEASE_PATH
+ - key: R_DOCKER_WORKER_RELEASE_PATH
value: /roomote/releases/worker-current.tar.gz
diff --git a/turbo.json b/turbo.json
index 53d2c46c..ba10686c 100644
--- a/turbo.json
+++ b/turbo.json
@@ -7,7 +7,7 @@
"MISE_CACHE_DIR",
"SKIP_ENV_VALIDATION",
"PREVIEW_ALLOWED_ORG_IDS",
- "ROUTER_DEBUG_CHANNEL_ID"
+ "R_ROUTER_DEBUG_CHANNEL_ID"
],
"tasks": {
"format": {},