-
Notifications
You must be signed in to change notification settings - Fork 50
Expand file tree
/
Copy pathtemplate.yaml
More file actions
369 lines (358 loc) · 20.7 KB
/
Copy pathtemplate.yaml
File metadata and controls
369 lines (358 loc) · 20.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
# Roomote Railway template definition.
#
# This file is the maintained source of truth for the official Roomote
# templates on Railway's marketplace. Two templates are published from this
# one spec — a develop-channel template tracking `:develop` and a
# main-channel template tracking `:main` — and they differ ONLY in the app
# image alias below; every service, variable, and note is otherwise
# identical. Railway does not consume this file directly: a maintainer
# mirrors it into Railway's Template Composer when creating or updating
# each published template. Values in `${{ ... }}` use Railway's
# reference-variable and template-function syntax and are resolved by
# Railway at deploy time.
#
# Design notes:
# - One shared `roomote-app` image runs web, api, controller, and bullmq;
# each service selects its process through the image entrypoint. Railway's
# custom start command bypasses the entrypoint, so every start command uses
# the full `/roomote/.docker/app/entrypoint.sh <service>` form (and minio
# uses `minio server ...`, not the bare `server ...` compose form).
# - Railway's Template Composer has no environment-level shared variables.
# The api service is the anchor: it defines every shared value once
# (including all `${{secret(n)}}` generated secrets, which MUST NOT be
# pasted into more than one service), and the other app services reference
# them as `${{api.NAME}}`.
# - 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
# reads the worker release version from the VERSION file inside
# worker-current.tar.gz. Production deployments can pin an immutable tag
# (v*, develop-<sha>, or main-<sha>) in the five image fields instead;
# nothing else needs editing. Railway does not redeploy when a mutable
# alias moves; redeploys can be automated against Railway's GraphQL API
# (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, daytona, blaxel, box, and azure
# also work).
# 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.
# The optional deploy-time prompts are R_APP_URL on api (see the annotation
# on that variable), for deployers who already own a domain, and
# R_LICENSE_KEY, for operators provisioning a paid-seat license. Override
# the pre-filled R_APP_URL default with https://<their-domain> before the
# 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.
# - All published images are public on GHCR: Railway pulls roomote-app and
# roomote-gbrain (and roomote-judgment), and hosted-sandbox builders pull roomote-worker,
# anonymously, so no registry credentials are needed.
# - The Brain (gbrain service) is optional and inert until
# a Brain provider key is filled in on api. It has no public domain:
# agents reach it only through the api proxy with their run token, and
# Railway private networking is IPv6-only, hence GBRAIN_BIND=::.
# - The CPU decision model (judgment service) is optional and inert until
# R_JUDGMENT_UPSTREAM_URL on api points at it and Settings selects it. Until then it idles without
# loading its model (JUDGMENT_PRELOAD=false), and it binds :: for the same
# IPv6-only private network.
# - R_APP_URL on api is the single canonical-origin knob; the other
# app services reference ${{api.R_APP_URL}}. Note api's own value
# is itself a reference (https://${{web.RAILWAY_PUBLIC_DOMAIN}}), so this
# is a reference to a reference. Verified working on a scratch deploy
# (2026-07-08): the app services receive the resolved URL, not a literal
# ${{...}} string. Re-check it on the scratch-project first-boot run
# after template changes (see "Maintaining the marketplace template" in
# README.md); if resolution ever regresses, fall back to repeating the
# raw https://${{web.RAILWAY_PUBLIC_DOMAIN}} value per service and accept
# the multi-service edit when changing domains. Do NOT flatten the
# ${{api.*}} references to raw values in the Composer — that silently
# breaks the one-variable custom-domain edit. A custom domain is either set
# at deploy time through the R_APP_URL prompt or attached later as
# a one-variable edit; see "Attaching a custom domain" in README.md.
# R_PUBLIC_URL is intentionally not set — the app falls
# back to R_APP_URL wherever it would apply. Railway does not
# update RAILWAY_PUBLIC_DOMAIN when a custom domain is attached, and
# browsing an origin the app was not told about fails auth with 403
# "Invalid origin".
# - SETUP_TOKEN=${{secret(32)}} gates the /setup wizard. The app requires a
# setup token everywhere except local development — a deployment without
# one cannot bootstrap its founding admin at all — so the template must
# generate it. The operator copies the value from the api service's
# Variables tab and pastes it into the wizard's token step (the URL form
# /setup?token=<value> also works).
# - R_PING_BASE_URL points the anonymous-analytics and version-check
# pings at the Roomote ping service (the code default is
# https://ping.roomote.dev). Admins can opt out of analytics in the setup
# wizard or Settings -> Misc; version checks ignore that setting.
# - Live previews ship disabled but ready: the preview-proxy service is in
# the template and boots healthy with the PREVIEW_* variables empty (it
# only hard-requires R_APP_URL, DATABASE_URL, and REDIS_URL, all in
# the shared block). Enabling previews needs a customer-owned wildcard
# custom domain plus the three PREVIEW_* values on api; see "Enabling
# live previews" in README.md. The PREVIEW_* variables on api are empty
# and marked OPTIONAL in the Template Composer so the deploy screen does
# not demand values for them.
# The ONLY line that differs between the two published templates. Use the
# alias for the channel being mirrored; deployments can also pin an
# immutable v* / develop-<sha> / main-<sha> tag instead.
image:
app: ghcr.io/roocodeinc/roomote-app:main # main-channel template (stable)
# app: ghcr.io/roocodeinc/roomote-app:develop # develop-channel template
worker: derived # ghcr.io/roocodeinc/roomote-worker:<baked RELEASE_VERSION>, resolved by the app
gbrain: ghcr.io/roocodeinc/roomote-gbrain:main # the Brain; same channel as app
judgment: ghcr.io/roocodeinc/roomote-judgment:main # CPU decision model; same channel as app
# The api service anchors every shared value. Other app services reference
# them with `${{api.NAME}}`.
services:
Postgres:
source: Railway managed PostgreSQL
notes: Provides `${{Postgres.DATABASE_URL}}`. Prefer the private-network URL.
Redis:
source: Railway managed Redis
notes: Provides `${{Redis.REDIS_URL}}`. Prefer the private-network URL.
minio:
image: ghcr.io/roocodeinc/roomote-minio:RELEASE.2025-10-15T17-29-55Z-f8ecd739@sha256:6e66a6df1e33ad3414c6cc29866bf06fec9d4e3cc7fc3897b9ef01d737d28c98
service_name: minio # rename from the image-derived default — a slash in the name breaks ${{minio.*}} references
start_command: minio server /data --console-address :9001
volume: /data
public_domain: http proxy port 9000 # S3 API; presigned artifact URLs use this domain
env:
MINIO_ROOT_USER: ${{api.S3_ACCESS_KEY_ID}}
MINIO_ROOT_PASSWORD: ${{api.S3_SECRET_ACCESS_KEY}}
notes: >
The artifact bucket is not auto-created. After the first deploy, create
`roomote-artifacts` once (see "Create the artifact bucket" in
README.md). Replacing MinIO with external S3/R2 is a config-only swap.
gbrain:
image: gbrain
public_domain: no # private network only; the api service proxies agent access
volume: /data
backup_schedules: [DAILY, WEEKLY] # configure both on the /data volume in Template Composer
env:
GBRAIN_DATABASE_URL: ${{Postgres.DATABASE_URL}}
# Railway's private network is IPv6-only, so the Brain must bind :: to
# be reachable at all. Compose deployments keep the 0.0.0.0 default.
GBRAIN_BIND: '::'
GBRAIN_ADMIN_BOOTSTRAP_TOKEN: ${{api.R_GBRAIN_ADMIN_TOKEN}}
# The Brain holds no provider key. It calls back into the api service,
# which injects whichever provider key an admin set in Settings, so
# switching or rotating that key never touches this service. Railway's
# private network is IPv6-only and the api container binds IPv4, so this
# goes over the public origin, exactly as TRPC_URL already does.
OPENAI_BASE_URL: https://${{api.RAILWAY_PUBLIC_DOMAIN}}/api/brain/inference
OPENAI_API_KEY: ${{api.R_BRAIN_GATEWAY_TOKEN}}
# Create-time, and it has to be the container that receives these: the
# embedding model and its width are decided when the Brain is created,
# and the gateway must never substitute a different one afterwards.
GBRAIN_EMBEDDING_MODEL: ${{api.R_BRAIN_EMBEDDING_MODEL}}
GBRAIN_EMBEDDING_DIMENSIONS: ${{api.R_BRAIN_EMBEDDING_DIMENSIONS}}
notes: >
Shared memory for agents: completed tasks and activity from connected
integrations become pages this deployment's agents consult, read-only,
through the api service — the Brain itself has no public domain and is
never reachable from the internet. It stays idle until
a Brain provider key is set on api, so deploying with both keys
empty costs only the container. The volume holds the corpus and has
daily plus weekly Railway backups; deleting it is recoverable (task
history and integration sources re-ingest) but not free. Delete this
service entirely if the deployment should have no memory.
judgment:
image: judgment
public_domain: no # private network only; Roomote's app services call it
volume: /models # model download cache
env:
# Railway's private network is IPv6-only.
JUDGMENT_BIND: '::'
# Idle without the model until the first decision, so a deployment that
# never switches it on pays for a small container only.
JUDGMENT_PRELOAD: 'false'
# Inference threads. Railway reports the host's cores, not a limit, so
# this is set explicitly; raise it with the service's vCPU allowance.
JUDGMENT_THREADS: '4'
# Bearer the app services present; generated on api.
JUDGMENT_API_KEY: ${{api.R_JUDGMENT_UPSTREAM_API_KEY}}
notes: >
Optional CPU decision model for routing and triage decisions (the Memory
check, reply addressing, task updates, delegated-task model choice). It
serves roomote/roomote-judgment-gliner, a public GLiNER 2.5 model
fine-tuned on Roomote's decisions, and stays unused until
R_JUDGMENT_UPSTREAM_URL on api points at it and the Roomote judgment
model is chosen in Settings > Models (see "Enabling the decision model"
in README.md). Idle it uses about 250 MB; once switched on, about
1.5 GB. Tool-call auto-approval still requires Jev. Delete this service
if the deployment should not run one.
api:
image: app
start_command: /roomote/.docker/app/entrypoint.sh api
pre_deploy_command: /roomote/.docker/app/entrypoint.sh db-migrate
public_domain: http proxy port 8080 # hosted workers and GitHub webhooks call this origin
healthcheck_path: /health/liveness
env:
R_APP_ENV: production
ROOMOTE_DOCKER_LOAD_ENV_FILE: 'false'
R_AUTO_GENERATE_KEYS: 'true'
ENCRYPTION_KEY: ${{secret(32)}}
# Transport secret between BullMQ Discord Gateway and API.
R_DISCORD_GATEWAY_SECRET: ${{secret(32)}}
# Optional paid-seat license key; takes precedence over Settings > Users.
R_LICENSE_KEY: ''
R_DISCORD_BOT_TOKEN: '' # optional; Discord can also be configured after boot in Settings
ARTIFACT_SIGNING_KEY: ${{secret(32)}}
DASHBOARD_PASSWORD: ${{secret(24)}}
S3_SECRET_ACCESS_KEY: ${{secret(32)}}
S3_ACCESS_KEY_ID: roomote
S3_REGION: us-east-1
S3_BUCKET_ARTIFACTS: roomote-artifacts
S3_AUTO_CREATE_BUCKET: 'true' # api creates the MinIO bucket at boot; no manual mc step
# Live-preview knobs, empty until previews are enabled (mark all
# three "optional" in the Composer so the deploy screen skips them).
# To enable: set to https://previews.<domain> / https://previews.<domain>
# / previews.<domain> after attaching the wildcard custom domain to
# the preview-proxy service — see "Enabling live previews" in README.md.
PREVIEW_PROXY_BASE_URL: ''
NEXT_PUBLIC_PREVIEW_PROXY_BASE_URL: ''
PREVIEW_DOMAINS: ''
SETUP_TOKEN: ${{secret(32)}} # gates /setup; required outside local dev (see design notes)
# The Brain (shared memory). Leave the key empty and the gbrain service
# idles unused: no ingestion, no delivery to agents, no cost beyond the
# container. Fill in either key (one key covers both embeddings and
# synthesis) and the deployment provisions its own scoped clients on
# the next tick. Deployers who do not want memory can delete the gbrain
# service; nothing else references it at runtime.
# Optional: pin the Brain's provider key here instead of setting it in
# Settings after boot. Either works, and Settings needs no redeploy.
R_BRAIN_OPENROUTER_API_KEY: ''
R_BRAIN_OPENAI_API_KEY: ''
# Shared secret the Brain presents to /api/brain/inference. Never an
# upstream provider key: the gateway swaps it for the configured one.
R_BRAIN_GATEWAY_TOKEN: ${{secret(48)}}
# Which models the Brain runs, in the configured provider's own naming
# (openai/gpt-5.6-luna on OpenRouter, gpt-5.6-luna on OpenAI). Empty
# means the deployment's defaults. The synthesis model can change at any
# time; the embedding pair must be set before the Brain's first boot,
# because it sizes a vector column that cannot be resized in place.
R_BRAIN_MODEL: ''
R_BRAIN_EMBEDDING_MODEL: ''
R_BRAIN_EMBEDDING_DIMENSIONS: ''
# Railway's private network is IPv6-only and never leaves the project,
# so the Brain is unreachable from the internet by construction.
R_GBRAIN_URL: http://${{gbrain.RAILWAY_PRIVATE_DOMAIN}}:8931
# Roomote uses this once to register its own scoped clients against the
# Brain. Generated here and passed to the gbrain
# service as its bootstrap token; gbrain requires >= 32 characters of
# [A-Za-z0-9_-], so keep the generated form.
R_GBRAIN_ADMIN_TOKEN: ${{secret(48)}}
# The CPU decision model's address. Leave empty and the judgment
# service idles unused. Set it to
# http://${{judgment.RAILWAY_PRIVATE_DOMAIN}}:8080, then choose the
# Roomote judgment model in Settings > Models, to use it for routing and
# triage decisions.
R_JUDGMENT_UPSTREAM_URL: ''
# Bearer for the judgment service, shared with it by reference.
R_JUDGMENT_UPSTREAM_API_KEY: ${{secret(48)}}
R_PING_BASE_URL: https://ping.roomote.dev # anonymous-analytics / version-check pings
R_INSTANCE_ID: '' # optional deterministic Ping instance identity
DEFAULT_COMPUTE_PROVIDER: modal
EXCLUDED_COMPUTE_PROVIDERS: docker
DATABASE_URL: ${{Postgres.DATABASE_URL}}
REDIS_URL: ${{Redis.REDIS_URL}}
# The single canonical-origin knob and one of the template's optional
# deploy-time prompts. In the Template Composer, give this variable the
# description below so Railway surfaces it on the deploy screen with
# the reference default pre-filled. Leaving the default keeps the
# Railway-generated domain; overriding it with https://<domain> sets a
# custom domain before first boot, so the setup wizard derives OAuth
# callback and webhook URLs from the right origin from day one. The
# deployer must still attach the domain to the web service and finish
# DNS after deploy — see "Attaching a custom domain" in README.md.
# Composer description: "The URL users will browse. Leave the default
# to use the Railway-generated domain. To use a custom domain, replace
# this with https://app.example.com — then, after deploying, add that
# 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}}
S3_ENDPOINT: http://${{minio.RAILWAY_PRIVATE_DOMAIN}}:9000
S3_PRESIGN_ENDPOINT: https://${{minio.RAILWAY_PUBLIC_DOMAIN}}
# The env block shared by web, controller, bullmq, and preview-proxy. In
# the Template Composer, paste it into each service (Railway has no
# per-template YAML anchors).
web:
image: app
start_command: /roomote/.docker/app/entrypoint.sh web
public_domain: http proxy port 8080 # this is the app origin users open
healthcheck_path: /health
env: &app_service_env
R_BRAIN_OPENROUTER_API_KEY: ${{api.R_BRAIN_OPENROUTER_API_KEY}}
R_BRAIN_OPENAI_API_KEY: ${{api.R_BRAIN_OPENAI_API_KEY}}
R_BRAIN_GATEWAY_TOKEN: ${{api.R_BRAIN_GATEWAY_TOKEN}}
R_BRAIN_MODEL: ${{api.R_BRAIN_MODEL}}
R_BRAIN_EMBEDDING_MODEL: ${{api.R_BRAIN_EMBEDDING_MODEL}}
R_GBRAIN_URL: ${{api.R_GBRAIN_URL}}
R_GBRAIN_ADMIN_TOKEN: ${{api.R_GBRAIN_ADMIN_TOKEN}}
R_JUDGMENT_UPSTREAM_URL: ${{api.R_JUDGMENT_UPSTREAM_URL}}
R_JUDGMENT_UPSTREAM_API_KEY: ${{api.R_JUDGMENT_UPSTREAM_API_KEY}}
R_APP_ENV: ${{api.R_APP_ENV}}
ROOMOTE_DOCKER_LOAD_ENV_FILE: ${{api.ROOMOTE_DOCKER_LOAD_ENV_FILE}}
R_AUTO_GENERATE_KEYS: ${{api.R_AUTO_GENERATE_KEYS}}
ENCRYPTION_KEY: ${{api.ENCRYPTION_KEY}}
R_DISCORD_GATEWAY_SECRET: ${{api.R_DISCORD_GATEWAY_SECRET}}
R_LICENSE_KEY: ${{api.R_LICENSE_KEY}}
R_DISCORD_BOT_TOKEN: ${{api.R_DISCORD_BOT_TOKEN}}
ARTIFACT_SIGNING_KEY: ${{api.ARTIFACT_SIGNING_KEY}}
DASHBOARD_PASSWORD: ${{api.DASHBOARD_PASSWORD}}
S3_SECRET_ACCESS_KEY: ${{api.S3_SECRET_ACCESS_KEY}}
S3_ACCESS_KEY_ID: ${{api.S3_ACCESS_KEY_ID}}
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}}
R_INSTANCE_ID: ${{api.R_INSTANCE_ID}}
# 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}}
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}}
DATABASE_URL: ${{Postgres.DATABASE_URL}}
REDIS_URL: ${{Redis.REDIS_URL}}
R_APP_URL: ${{api.R_APP_URL}}
TRPC_URL: ${{api.TRPC_URL}}
S3_ENDPOINT: ${{api.S3_ENDPOINT}}
S3_PRESIGN_ENDPOINT: ${{api.S3_PRESIGN_ENDPOINT}}
controller:
image: app
start_command: /roomote/.docker/app/entrypoint.sh controller
public_domain: no
env:
<<: *app_service_env
# Optional capped OpenRouter key mapped into nested Roomote deployments.
SANDBOX_OPENROUTER_API_KEY: ''
# The worker release archive is baked into the app image; the
# controller uploads it into hosted sandboxes at spawn time.
# The version-less name works because the controller reads the release
# 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
bullmq:
image: app
start_command: /roomote/.docker/app/entrypoint.sh bullmq
public_domain: no
env: *app_service_env
# Boots healthy with the PREVIEW_* variables empty and serves nothing
# until previews are enabled: attach `previews.<your-domain>` and the
# `*.previews.<your-domain>` wildcard as custom domains here, fill the
# three PREVIEW_* values on api, and opt in from Settings -> Live
# Previews. See "Enabling live previews" in README.md.
preview-proxy:
image: app
start_command: /roomote/.docker/app/entrypoint.sh preview-proxy
public_domain: http proxy port 8080 # plus the previews custom domains when enabled
healthcheck_path: /health
env: *app_service_env