Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
78 changes: 65 additions & 13 deletions packages/matrix/docker/synapse/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as path from 'path';
import * as os from 'os';
import * as crypto from 'crypto';
import * as net from 'net';
import * as fse from 'fs-extra';
import { request } from '@playwright/test';
import {
Expand Down Expand Up @@ -41,13 +42,51 @@ export interface SynapseInstance extends SynapseConfig {

const synapses = new Map<string, SynapseInstance>();

function findAvailablePort(preferred?: number): Promise<number> {
return new Promise((resolve, reject) => {
let server = net.createServer();

server.on('error', (error: NodeJS.ErrnoException) => {
server.close();
if (preferred != null && error.code === 'EADDRINUSE') {
findAvailablePort(undefined).then(resolve, reject);
return;
}
reject(error);
});

server.listen(preferred ?? 0, '127.0.0.1', () => {
let address = server.address();
if (!address || typeof address === 'string') {
server.close(() =>
reject(new Error('Could not determine available port')),
);
return;
}
let { port } = address;
server.close((error) => {
if (error) {
reject(error);
return;
}
resolve(port);
});
});
});
}

function randB64Bytes(numBytes: number): string {
return crypto.randomBytes(numBytes).toString('base64').replace(/=*$/, '');
}

export async function cfgDirFromTemplate(
template: string,
dataDir?: string,
options?: {
publicBaseUrl?: string;
host?: string;
port?: number;
},
): Promise<SynapseConfig> {
const templateDir = path.join(__dirname, template);

Expand All @@ -69,7 +108,9 @@ export async function cfgDirFromTemplate(
const macaroonSecret = randB64Bytes(16);
const formSecret = randB64Bytes(16);

const baseUrl = `http://${SYNAPSE_IP_ADDRESS}:${SYNAPSE_PORT}`;
const host = options?.host ?? SYNAPSE_IP_ADDRESS;
const port = options?.port ?? SYNAPSE_PORT;
const baseUrl = options?.publicBaseUrl ?? `http://${host}:${port}`;

// now copy homeserver.yaml, applying substitutions
console.log(`Gen ${path.join(templateDir, 'homeserver.yaml')}`);
Expand All @@ -95,8 +136,8 @@ export async function cfgDirFromTemplate(
);

return {
port: SYNAPSE_PORT,
host: SYNAPSE_IP_ADDRESS,
port,
host,
baseUrl,
configDir,
registrationSecret,
Expand Down Expand Up @@ -136,9 +177,18 @@ export async function synapseStart(
}
await Promise.allSettled(stopPromises);
}
let useDynamicHostPort = Boolean(
isEnvironmentMode() || opts?.dynamicHostPort,
);
let hostPort = useDynamicHostPort ? await findAvailablePort() : SYNAPSE_PORT;
Comment thread
habdelra marked this conversation as resolved.
Outdated
const synCfg = await cfgDirFromTemplate(
opts?.template ?? 'test',
opts?.dataDir,
{
host: useDynamicHostPort ? '127.0.0.1' : SYNAPSE_IP_ADDRESS,
port: hostPort,
publicBaseUrl: `http://localhost:${hostPort}`,
},
);
let containerName =
opts?.containerName ||
Expand All @@ -157,12 +207,10 @@ export async function synapseStart(
'-v',
`${path.join(__dirname, 'templates')}:/custom/templates/`,
];
if (isEnvironmentMode() || opts?.dynamicHostPort) {
// Dynamic host port, with fixed container IP only when not running in branch mode
if (!isEnvironmentMode()) {
dockerParams.push(`--ip=${synCfg.host}`);
}
dockerParams.push('-p', '0:8008/tcp', '--network=boxel');
if (useDynamicHostPort) {
// In dynamic-host-port mode multiple harnesses may run concurrently, so
// we must not claim the shared fixed Synapse container IP.
dockerParams.push('-p', `${hostPort}:8008/tcp`, '--network=boxel');
} else {
dockerParams.push(
`--ip=${synCfg.host}`,
Expand All @@ -180,7 +228,7 @@ export async function synapseStart(
runAsUser: true,
});

console.log(`Started synapse with id ${synapseId} on port ${synCfg.port}`);
console.log(`Started synapse with id ${synapseId} on port ${hostPort}`);

// Await Synapse healthcheck
await dockerExec({
Expand All @@ -199,9 +247,13 @@ export async function synapseStart(
],
});

let hostPort = synCfg.port;
if (isEnvironmentMode() || opts?.dynamicHostPort) {
hostPort = await resolveHostPort(synapseId);
if (useDynamicHostPort) {
let resolvedPort = await resolveHostPort(synapseId);
if (resolvedPort !== hostPort) {
throw new Error(
`Synapse started on unexpected host port ${resolvedPort}; expected ${hostPort}`,
);
}
console.log(`Synapse dynamic host port: ${hostPort}`);
}

Expand Down
1 change: 1 addition & 0 deletions packages/realm-server/scripts/start-production.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ EXTERNAL_CATALOG_REALM_URL="${RESOLVED_EXTERNAL_CATALOG_REALM_URL:-$DEFAULT_EXTE
NODE_NO_WARNINGS=1 \
LOW_CREDIT_THRESHOLD=2000 \
MATRIX_URL=https://matrix.boxel.ai \
MATRIX_SERVER_NAME=boxel.ai \
BOXEL_HOST_URL=https://app.boxel.ai \
REALM_SERVER_MATRIX_USERNAME=realm_server \
PUBLISHED_REALM_BOXEL_SPACE_DOMAIN='boxel.space' \
Expand Down
1 change: 1 addition & 0 deletions packages/realm-server/scripts/start-staging.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ EXTERNAL_CATALOG_REALM_URL="${RESOLVED_EXTERNAL_CATALOG_REALM_URL:-$DEFAULT_EXTE
NODE_NO_WARNINGS=1 \
LOW_CREDIT_THRESHOLD=2000 \
MATRIX_URL=https://matrix-staging.stack.cards \
MATRIX_SERVER_NAME=stack.cards \
BOXEL_HOST_URL=https://realms-staging.stack.cards \
REALM_SERVER_MATRIX_USERNAME=realm_server \
PUBLISHED_REALM_BOXEL_SPACE_DOMAIN='staging.boxel.dev' \
Expand Down
4 changes: 3 additions & 1 deletion packages/realm-server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,9 @@ export class RealmServer {
hostsOwnAssets: false,
assetsURL: this.assetsURL.href,
matrixURL: this.matrixClient.matrixURL.href.replace(/\/$/, ''),
matrixServerName: this.matrixClient.matrixURL.hostname,
matrixServerName:
process.env.MATRIX_SERVER_NAME ||
this.matrixClient.matrixURL.hostname,
Comment thread
habdelra marked this conversation as resolved.
realmServerURL: this.serverURL.href,
resolvedBaseRealmURL: rewriteRealmURL(config.resolvedBaseRealmURL),
resolvedCatalogRealmURL: rewriteRealmURL(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ export async function startIsolatedRealmStack({
GRAFANA_SECRET,
HOST_URL: context.hostURL,
MATRIX_URL: context.matrixURL,
MATRIX_SERVER_NAME: new URL(context.matrixURL).hostname,
Comment thread
habdelra marked this conversation as resolved.
MATRIX_REGISTRATION_SHARED_SECRET: context.matrixRegistrationSecret,
REALM_SERVER_MATRIX_USERNAME: DEFAULT_MATRIX_SERVER_USERNAME,
REALM_SERVER_FULL_INDEX_ON_STARTUP: String(fullIndexOnStartup),
Expand Down
27 changes: 22 additions & 5 deletions packages/software-factory/src/harness/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -781,19 +781,36 @@ export async function stopManagedProcess(proc: SpawnedProcess): Promise<void> {
if (proc.exitCode !== null) {
return;
}
let stopped = new Promise<void>((resolve) => {
let stopped = new Promise<boolean>((resolve) => {
let onMessage = (message: unknown) => {
if (message === 'stopped') {
proc.off('message', onMessage);
resolve();
resolve(true);
}
};
proc.on('message', onMessage);
});
let exited = new Promise<void>((resolve) => {
let onExit = () => {
proc.off('exit', onExit);
proc.off('error', onExit);
resolve();
};
proc.on('exit', onExit);
proc.on('error', onExit);
});
proc.send('stop');
await Promise.race([
let stoppedGracefully = await Promise.race([
stopped,
Comment thread
habdelra marked this conversation as resolved.
new Promise<void>((resolve) => setTimeout(resolve, 15_000)),
new Promise<false>((resolve) => setTimeout(() => resolve(false), 15_000)),
]);
proc.send('kill');
if (!stoppedGracefully && proc.exitCode === null) {
proc.send('kill');
}
if (proc.exitCode === null) {
await Promise.race([
exited,
new Promise<void>((resolve) => setTimeout(resolve, 15_000)),
]);
}
Comment thread
habdelra marked this conversation as resolved.
}
Loading