Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .agents/skills/mock-slack-testing/SKILL.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
6 changes: 3 additions & 3 deletions .agents/skills/mock-telegram-testing/SKILL.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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` |
Expand Down Expand Up @@ -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=<any-shared-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
Expand Down
8 changes: 4 additions & 4 deletions .env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand All @@ -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=
40 changes: 20 additions & 20 deletions .env.production.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:<baked release version>.
# 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=
Expand Down Expand Up @@ -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=
Expand Down
2 changes: 1 addition & 1 deletion .roomote/environments/roomote.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
44 changes: 22 additions & 22 deletions SELF_HOSTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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**.

Expand Down Expand Up @@ -562,77 +562,77 @@ 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
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
BLAXEL_IMAGE=sandbox/roomote-worker:<version>
```

`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:<version>` 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
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/__tests__/live-server.integration.test.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions apps/api/src/__tests__/logging.test.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/api/src/handlers/ado/__tests__/index.test.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/api/src/handlers/ado/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/handlers/bitbucket/__tests__/index.test.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/api/src/handlers/bitbucket/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/handlers/gitea/__tests__/index.test.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/api/src/handlers/gitea/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading