Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
fe0ed0c
Add preliminary environment mode for Matrix tests
backspace Mar 21, 2026
a65ad04
Add prefix for start-server-and-test calls
backspace Mar 21, 2026
b00ffc5
Fix some services in environment mode
backspace Mar 23, 2026
8580d46
Add more domain conditionals
backspace Mar 23, 2026
08243a1
Fix more URLs and ports
backspace Mar 23, 2026
fcae21b
Change prerenderer to be isolated
backspace Mar 23, 2026
dec1d46
Fix registration order
backspace Mar 23, 2026
79dcb20
Fix prerender URL in environment mode
backspace Mar 23, 2026
cfde967
Add Traefik-awareness for some startup scripts
backspace Mar 23, 2026
fdc3504
Add paralell infrastructure for Matrix tests
backspace Mar 23, 2026
62e2909
Change prerender server in environment mode
backspace Mar 23, 2026
068fdd2
Add more environment mode fixes
backspace Mar 26, 2026
7131eb2
Merge remote-tracking branch 'origin/main' into matrix/tests-environm…
backspace Mar 26, 2026
460b438
Fix more setup
backspace Mar 26, 2026
7edddb6
Change isolated realm server resolution
backspace Mar 26, 2026
f6758de
Add missing PATH setup
backspace Mar 26, 2026
5b94b32
Merge remote-tracking branch 'origin/main' into matrix/tests-environm…
backspace Mar 26, 2026
e8927aa
Merge remote-tracking branch 'origin/main' into matrix/tests-environm…
backspace Mar 26, 2026
8c5dba9
Add pooling maximum for isolated realm server
backspace Mar 26, 2026
128f720
Merge remote-tracking branch 'origin/main' into matrix/tests-environm…
backspace Mar 26, 2026
ff0be17
Merge remote-tracking branch 'origin/main' into matrix/tests-environm…
backspace Mar 27, 2026
bdcdcf3
matrix: Environment-mode support for Matrix Playwright tests
backspace Apr 1, 2026
91275af
matrix: Use separate test Synapse domain via realm server config rewrite
backspace Apr 1, 2026
45fbfde
Merge remote-tracking branch 'origin/main' into matrix/tests-environm…
backspace Apr 2, 2026
6dbe7fd
Merge remote-tracking branch 'origin/main' into matrix/tests-environm…
backspace Apr 9, 2026
3cb2850
Merge remote-tracking branch 'origin/main' into matrix/tests-environm…
backspace Apr 15, 2026
36bf3be
Fix premature removal of routing
backspace Apr 16, 2026
cb094ce
Remove unused task
backspace Apr 17, 2026
0608353
Add more environment mode fixes
backspace Apr 17, 2026
54e7e8d
Remove unused function
backspace Apr 17, 2026
557076c
Remove superfluous intermediate variable
backspace Apr 17, 2026
a1a3563
Fix SMTP setup in environment mode Matrix tests
backspace Apr 17, 2026
493a3dc
Fix publishing domains
backspace Apr 17, 2026
672fb1d
Remove domains from file URL expectations
backspace Apr 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions mise-tasks/dev
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
#MISE description="Start full dev stack (realm server, workers, test realms)"
#MISE dir="packages/realm-server"

# Add node_modules/.bin to PATH (mise run bypasses pnpm which normally does this)
PATH="$(pwd)/../../node_modules/.bin:$(pwd)/node_modules/.bin:$PATH"

. "$(cd "$(dirname "$0")" && pwd)/lib/dev-common.sh"

WAIT_ON_TIMEOUT=7200000 NODE_NO_WARNINGS=1 start-server-and-test \
Expand Down
22 changes: 22 additions & 0 deletions mise-tasks/lib/env-vars.sh
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ if [ -n "${BOXEL_ENVIRONMENT:-}" ]; then
# Paths
export REALMS_ROOT="./realms/${ENV_SLUG}"
export REALMS_TEST_ROOT="./realms/${ENV_SLUG}_test"

# Matrix test services (isolated realm server + worker for Playwright tests)
export MATRIX_TEST_REALM_URL="http://realm-matrix-test.${ENV_SLUG}.localhost"
export MATRIX_TEST_REALM_PORT=0
export MATRIX_TEST_WORKER_PORT=0

export SMTP_URL="http://smtp.${ENV_SLUG}.localhost"
export SMTP_PORT=0
else
# Capture previous ENV_MODE before resetting it, so we can detect transitions
_PREV_ENV_MODE="${ENV_MODE:-}"
Expand Down Expand Up @@ -108,6 +116,13 @@ else
# Paths
export REALMS_ROOT="./realms/localhost_4201"
export REALMS_TEST_ROOT="./realms/localhost_4202"

# Matrix test services
export MATRIX_TEST_REALM_URL="http://localhost:4205"
export MATRIX_TEST_REALM_PORT=4205
export MATRIX_TEST_WORKER_PORT=4232
export SMTP_URL="http://localhost:5001"
export SMTP_PORT=5001
else
# Fresh standard mode or non-env-mode shell:
# use :- so production/staging env vars are not clobbered.
Expand Down Expand Up @@ -138,6 +153,13 @@ else
# Paths
export REALMS_ROOT="${REALMS_ROOT:-./realms/localhost_4201}"
export REALMS_TEST_ROOT="${REALMS_TEST_ROOT:-./realms/localhost_4202}"

# Matrix test services
export MATRIX_TEST_REALM_URL="${MATRIX_TEST_REALM_URL:-http://localhost:4205}"
export MATRIX_TEST_REALM_PORT="${MATRIX_TEST_REALM_PORT:-4205}"
export MATRIX_TEST_WORKER_PORT="${MATRIX_TEST_WORKER_PORT:-4232}"
export SMTP_URL="${SMTP_URL:-http://localhost:5001}"
export SMTP_PORT="${SMTP_PORT:-5001}"
fi

unset _PREV_ENV_MODE
Expand Down
29 changes: 23 additions & 6 deletions mise-tasks/services/realm-server-base
Original file line number Diff line number Diff line change
@@ -1,32 +1,49 @@
#!/bin/sh
#MISE description="Start base realm server only"
#MISE depends=["infra:ensure-pg"]
#MISE depends=["infra:ensure-traefik", "infra:ensure-pg"]
#MISE dir="packages/realm-server"

if [ -z "$MATRIX_REGISTRATION_SHARED_SECRET" ]; then
MATRIX_REGISTRATION_SHARED_SECRET=$(ts-node --transpileOnly ./scripts/matrix-registration-secret.ts)
export MATRIX_REGISTRATION_SHARED_SECRET
fi

# The base-only realm server uses dedicated port/db/paths that differ from
# the main development realm server. In env mode, use the env vars; in
# standard mode, use the base-specific defaults.
if [ -n "$ENV_MODE" ]; then
WORKER_MANAGER_ARG="--workerManagerUrl=${WORKER_MGR_URL}"
REALM_BASE_PORT="${REALM_PORT}"
REALM_BASE_DB="${PGDATABASE}"
REALM_BASE_ROOT="${REALMS_ROOT}"
REALM_BASE_TO_URL="${REALM_BASE_URL}/base/"
else
WORKER_MANAGER_ARG="$1"
REALM_BASE_PORT=4201
REALM_BASE_DB=boxel_base
REALM_BASE_ROOT="./realms/localhost_4201_base"
REALM_BASE_TO_URL="http://localhost:4201/base/"
fi

NODE_ENV=development \
NODE_NO_WARNINGS=1 \
PGPORT="${PGPORT}" \
PGDATABASE=boxel_base \
PGDATABASE="${REALM_BASE_DB}" \
REALM_SERVER_SECRET_SEED="mum's the word" \
REALM_SECRET_SEED="shhh! it's a secret" \
GRAFANA_SECRET="shhh! it's a secret" \
MATRIX_URL="${MATRIX_URL_VAL}" \
REALM_SERVER_MATRIX_USERNAME=realm_server \
ts-node \
--transpileOnly main \
--port=4201 \
--port="${REALM_BASE_PORT}" \
--matrixURL="${MATRIX_URL_VAL}" \
--realmsRootPath='./realms/localhost_4201_base' \
--realmsRootPath="${REALM_BASE_ROOT}" \
--prerendererUrl="${PRERENDER_MGR_URL}" \
--migrateDB \
$1 \
$WORKER_MANAGER_ARG \
\
--path='../base' \
--username='base_realm' \
--fromUrl='https://cardstack.com/base/' \
--toUrl='http://localhost:4201/base/'
--toUrl="${REALM_BASE_TO_URL}"
19 changes: 15 additions & 4 deletions mise-tasks/services/worker-base
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
#!/bin/sh
#MISE description="Start worker manager for base realm only"
#MISE depends=["infra:ensure-pg", "infra:wait-for-prerender"]
#MISE depends=["infra:ensure-traefik", "infra:ensure-pg", "infra:wait-for-prerender"]
#MISE dir="packages/realm-server"

# The base-only worker uses dedicated port/db that differ from the main
# development worker (WORKER_PORT/PGDATABASE). In env mode, use the env
# vars; in standard mode, use the base-specific defaults.
if [ -n "$ENV_MODE" ]; then
WORKER_BASE_PORT="${WORKER_PORT}"
WORKER_BASE_DB="${PGDATABASE}"
else
WORKER_BASE_PORT=4213
WORKER_BASE_DB=boxel_base
fi

NODE_ENV=development \
NODE_NO_WARNINGS=1 \
NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=4096}" \
PGPORT="${PGPORT}" \
PGDATABASE=boxel_base \
PGDATABASE="${WORKER_BASE_DB}" \
REALM_SECRET_SEED="shhh! it's a secret" \
REALM_SERVER_MATRIX_USERNAME=realm_server \
LOW_CREDIT_THRESHOLD=2000 \
ts-node \
--transpileOnly worker-manager \
--port=4213 \
--port="${WORKER_BASE_PORT}" \
--matrixURL="${MATRIX_URL_VAL}" \
--prerendererUrl="${PRERENDER_MGR_URL}" \
\
--fromUrl='https://cardstack.com/base/' \
--toUrl='http://localhost:4201/base/'
--toUrl="${REALM_BASE_URL:-http://localhost:4201}/base/"
20 changes: 16 additions & 4 deletions packages/host/app/services/host-mode-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,22 @@ export default class HostModeService extends Service {
}

get originIsNotMatrixTests() {
return (
this.hostModeOrigin !== 'http://localhost:4202' &&
this.hostModeOrigin !== 'http://localhost:4205'
);
let origin = this.hostModeOrigin;
if (
origin === 'http://localhost:4202' ||
origin === 'http://localhost:4205'
) {
return false;
}
// In environment mode, the test realm server origin matches
// realmServerURL — treat it as a non-host-mode (operator mode) origin.
if (config.realmServerURL) {
let realmServerOrigin = new URL(config.realmServerURL).origin;
if (origin === realmServerOrigin) {
return false;
}
}
return true;
}

get realmURL() {
Expand Down
55 changes: 47 additions & 8 deletions packages/matrix/docker/smtp4dev.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,72 @@
import { dockerCreateNetwork, dockerRun, dockerStop, dockerRm } from './index';
import {
isEnvironmentMode,
getEnvironmentSlug,
registerServiceWithTraefik,
deregisterServiceFromTraefik,
} from '../helpers/environment-config';
import { execSync } from 'child_process';

interface Options {
mailClientPort?: number;
traefikServiceName?: string;
}

let _smtpServiceName = 'smtp';

function smtpContainerName(): string {
if (isEnvironmentMode()) {
return `boxel-${_smtpServiceName}-${getEnvironmentSlug()}`;
}
return 'boxel-smtp';
}

export async function smtpStart(opts?: Options) {
if (opts?.traefikServiceName) {
_smtpServiceName = opts.traefikServiceName;
}
let containerName = smtpContainerName();
try {
await smtpStop();
} catch (e: any) {
if (!e.message.includes('No such container')) {
throw e;
}
}
let mailClientPort = opts?.mailClientPort ?? 5001;
let portMapping = `${mailClientPort}:80`;
let envMode = isEnvironmentMode();
let mailClientPort = envMode
? 0
: (opts?.mailClientPort ?? parseInt(process.env.SMTP_PORT || '5001', 10));
let portMapping = envMode ? '0:80' : `${mailClientPort}:80`;
await dockerCreateNetwork({ networkName: 'boxel' });
const containerId = await dockerRun({
image: 'rnwood/smtp4dev:v3.1',
containerName: 'boxel-smtp',
containerName,
dockerParams: ['-p', portMapping, '--network=boxel'],
});

console.log(
`Started smtp4dev with id ${containerId} mapped to host port ${mailClientPort}.`,
);
if (envMode) {
let portOutput = execSync(`docker port ${containerId} 80/tcp`, {
encoding: 'utf-8',
}).trim();
let hostPort = parseInt(portOutput.split('\n')[0].split(':').pop()!, 10);
registerServiceWithTraefik(_smtpServiceName, hostPort);
console.log(
`Started smtp4dev with id ${containerId} on dynamic port ${hostPort} (Traefik).`,
);
} else {
console.log(
`Started smtp4dev with id ${containerId} mapped to host port ${mailClientPort}.`,
);
}
return containerId;
}

export async function smtpStop() {
await dockerStop({ containerId: 'boxel-smtp' });
await dockerRm({ containerId: 'boxel-smtp' });
let containerName = smtpContainerName();
if (isEnvironmentMode()) {
deregisterServiceFromTraefik(_smtpServiceName);
}
await dockerStop({ containerId: containerName });
await dockerRm({ containerId: containerName });
}
16 changes: 14 additions & 2 deletions packages/matrix/docker/synapse/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
isEnvironmentMode,
getSynapseContainerName,
getSynapseURL,
registerSynapseWithTraefik,
registerServiceWithTraefik,
} from '../../helpers/environment-config';

export const SYNAPSE_IP_ADDRESS = '172.20.0.5';
Expand Down Expand Up @@ -92,6 +92,7 @@ export async function cfgDirFromTemplate(
publicBaseUrl?: string;
host?: string;
port?: number;
smtpHost?: string;
},
): Promise<SynapseConfig> {
const templateDir = path.join(__dirname, template);
Expand Down Expand Up @@ -128,6 +129,10 @@ export async function cfgDirFromTemplate(
hsYaml = hsYaml.replace(/{{MACAROON_SECRET_KEY}}/g, macaroonSecret);
hsYaml = hsYaml.replace(/{{FORM_SECRET}}/g, formSecret);
hsYaml = hsYaml.replace(/{{PUBLIC_BASEURL}}/g, baseUrl);
hsYaml = hsYaml.replace(
/{{SMTP_HOST}}/g,
options?.smtpHost ?? 'boxel-smtp',
);

await fse.writeFile(path.join(configDir, 'homeserver.yaml'), hsYaml);

Expand Down Expand Up @@ -157,6 +162,8 @@ interface StartOptions {
dataDir?: string;
containerName?: string;
suppressRegistrationSecretFile?: true;
traefikServiceName?: string;
smtpHost?: string;
dynamicHostPort?: true;
}

Expand Down Expand Up @@ -200,6 +207,7 @@ export async function synapseStart(
host: useDynamicHostPort ? '127.0.0.1' : SYNAPSE_IP_ADDRESS,
port: hostPort,
publicBaseUrl: `http://localhost:${hostPort}`,
smtpHost: opts?.smtpHost,
});
containerName =
opts?.containerName ||
Expand Down Expand Up @@ -230,6 +238,9 @@ export async function synapseStart(
);
}

// Clean up stale container from a previous interrupted run
await dockerStop({ containerId: containerName }).catch(() => {});

try {
synapseId = await dockerRun({
image: 'matrixdotorg/synapse:v1.126.0',
Expand Down Expand Up @@ -286,7 +297,8 @@ export async function synapseStart(
}

if (isEnvironmentMode()) {
registerSynapseWithTraefik(hostPort);
let synapseServiceName = opts?.traefikServiceName || 'matrix';
registerServiceWithTraefik(synapseServiceName, hostPort);
}

const synapse: SynapseInstance = {
Expand Down
6 changes: 3 additions & 3 deletions packages/matrix/docker/synapse/test/homeserver.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
server_name: "localhost"
pid_file: /data/homeserver.pid
public_baseurl: "http://localhost:8008"
public_baseurl: "{{PUBLIC_BASEURL}}"
listeners:
- port: 8008
tls: false
Expand Down Expand Up @@ -85,14 +85,14 @@ ui_auth:
session_timeout: "300s"

email:
smtp_host: "boxel-smtp"
smtp_host: "{{SMTP_HOST}}"
smtp_port: 25
enable_tls: false
notif_from: "%(app)s <noreply@example.com>"
app_name: "Boxel"
enable_notifs: true
notif_for_new_users: false
client_base_url: "http://localhost:4200"
client_base_url: "{{PUBLIC_BASEURL}}"
validation_token_lifetime: "1h"
subjects:
email_validation: "%(app)s Email Validation"
Expand Down
Loading
Loading