Skip to content
Open
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
2 changes: 1 addition & 1 deletion packages/host/app/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Router.map(function () {
});
this.route('module', { path: '/module/:id/:nonce/:options' });
this.route('connect', { path: '/connect/:origin' });
this.route('standby');
this.route('standby', { path: '/_standby' });
this.route('command-runner', {
path: '/command-runner/:request_id/:nonce',
});
Expand Down
42 changes: 30 additions & 12 deletions packages/matrix/docker/synapse/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,18 @@ interface StartOptions {
dataDir?: string;
containerName?: string;
suppressRegistrationSecretFile?: true;
dynamicHostPort?: true;
}

async function resolveHostPort(synapseId: string): Promise<number> {
let { execSync } = await import('child_process');
let portOutput = execSync(`docker port ${synapseId} 8008/tcp`, {
encoding: 'utf-8',
}).trim();
let firstLine = portOutput.split('\n')[0];
return parseInt(firstLine.split(':').pop()!, 10);
}

export async function synapseStart(
opts?: StartOptions,
stopExisting = true,
Expand Down Expand Up @@ -146,8 +157,11 @@ export async function synapseStart(
'-v',
`${path.join(__dirname, 'templates')}:/custom/templates/`,
];
if (isEnvironmentMode()) {
// Branch mode: dynamic host port, no fixed IP
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');
} else {
dockerParams.push(
Expand Down Expand Up @@ -185,19 +199,23 @@ export async function synapseStart(
],
});

// In branch mode, read the dynamic host port and register with Traefik
if (isEnvironmentMode()) {
let { execSync } = await import('child_process');
let portOutput = execSync(`docker port ${synapseId} 8008/tcp`, {
encoding: 'utf-8',
}).trim();
let firstLine = portOutput.split('\n')[0];
let hostPort = parseInt(firstLine.split(':').pop()!, 10);
let hostPort = synCfg.port;
if (isEnvironmentMode() || opts?.dynamicHostPort) {
hostPort = await resolveHostPort(synapseId);
console.log(`Synapse dynamic host port: ${hostPort}`);
}

if (isEnvironmentMode()) {
registerSynapseWithTraefik(hostPort);
}

const synapse: SynapseInstance = { synapseId, ...synCfg };
const synapse: SynapseInstance = {
synapseId,
...synCfg,
host: '127.0.0.1',
port: hostPort,
baseUrl: `http://localhost:${hostPort}`,
};
synapses.set(synapseId, synapse);

function cleanupRegistrationSecret() {
Expand Down Expand Up @@ -250,7 +268,7 @@ export async function registerUser(
admin = false,
displayName?: string,
): Promise<Credentials> {
const url = `${getSynapseURL()}/_synapse/admin/v1/register`;
const url = `${getSynapseURL(synapse)}/_synapse/admin/v1/register`;
const context = await request.newContext({ baseURL: url });
const { nonce } = await (await context.get(url)).json();
const mac = admin
Expand Down
21 changes: 12 additions & 9 deletions packages/matrix/helpers/environment-config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { execSync } from 'child_process';
import {
writeFileSync,
renameSync,
unlinkSync,
} from 'fs';
import { writeFileSync, renameSync, unlinkSync } from 'fs';
import { join, resolve } from 'path';
import yaml from 'yaml';

Expand Down Expand Up @@ -64,7 +60,16 @@ export function getSynapseContainerName(): string {
return 'boxel-synapse';
}

export function getSynapseURL(): string {
export function getSynapseURL(synapse?: {
baseUrl?: string;
port?: number;
}): string {
if (synapse?.baseUrl) {
return synapse.baseUrl;
}
if (synapse?.port != null) {
return `http://localhost:${synapse.port}`;
}
if (!isEnvironmentMode()) {
return 'http://localhost:8008';
}
Expand Down Expand Up @@ -110,9 +115,7 @@ export function registerSynapseWithTraefik(hostPort: number): void {
};

atomicWrite(configPath, yaml.stringify(config));
console.log(
`Registered Synapse at ${hostname} -> localhost:${hostPort}`,
);
console.log(`Registered Synapse at ${hostname} -> localhost:${hostPort}`);
}

export function deregisterSynapseFromTraefik(): void {
Expand Down
4 changes: 0 additions & 4 deletions packages/matrix/helpers/isolated-realm-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ export async function startPrerenderServer(
): Promise<RunningPrerenderServer> {
let port = await findAvailablePort(options?.port ?? DEFAULT_PRERENDER_PORT);
let url = `http://localhost:${port}`;
let silent = process.env.SOFTWARE_FACTORY_PRERENDER_SILENT !== '0';
let env = {
...process.env,
NODE_ENV: process.env.NODE_ENV ?? 'development',
Expand All @@ -152,9 +151,6 @@ export async function startPrerenderServer(
'prerender/prerender-server',
`--port=${port}`,
];
if (silent) {
prerenderArgs.push('--silent');
}

let child = spawn('ts-node', prerenderArgs, {
cwd: realmServerDir,
Expand Down
12 changes: 12 additions & 0 deletions packages/postgres/pg-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ function config() {

type Config = ReturnType<typeof config>;

function configuredPoolMax(): number | undefined {
let rawValue = process.env.PG_POOL_MAX;
if (!rawValue) {
return undefined;
}

let value = Number(rawValue);
return Number.isInteger(value) && value > 0 ? value : undefined;
}

export class PgAdapter implements DBAdapter {
readonly kind = 'pg';
#isClosed = false;
Expand All @@ -46,13 +56,15 @@ export class PgAdapter implements DBAdapter {
}
this.config = config();
let { user, host, database, password, port } = this.config;
let max = configuredPoolMax();
log.debug(`connecting to DB ${this.url}`);
this.pool = new Pool({
user,
host,
database,
password,
port,
...(max ? { max } : {}),
});
}

Expand Down
20 changes: 20 additions & 0 deletions packages/realm-server/lib/runtime-metadata-file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { mkdirSync, renameSync, writeFileSync } from 'fs';
import { dirname, join } from 'path';

export function writeRuntimeMetadataFile(
runtimeMetadataFile: string | undefined,
tempFilePrefix: string,
payload: unknown,
): void {
if (!runtimeMetadataFile) {
return;
}

mkdirSync(dirname(runtimeMetadataFile), { recursive: true });
let tempFile = join(
dirname(runtimeMetadataFile),
`.${tempFilePrefix}.${process.pid}.${Date.now()}.tmp`,
);
writeFileSync(tempFile, JSON.stringify(payload, null, 2));
renameSync(tempFile, runtimeMetadataFile);
}
14 changes: 14 additions & 0 deletions packages/realm-server/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,18 @@ import {
registerService,
deregisterEnvironment,
} from './lib/dev-service-registry';
import { writeRuntimeMetadataFile } from './lib/runtime-metadata-file';

(globalThis as any).ContentTagGlobal = ContentTagGlobal;

let log = logger('main');
const runtimeMetadataFile =
process.env.SOFTWARE_FACTORY_REALM_SERVER_METADATA_FILE;

function writeRuntimeMetadata(payload: unknown): void {
writeRuntimeMetadataFile(runtimeMetadataFile, 'realm-server', payload);
}

if (process.env.NODE_ENV === 'test') {
(globalThis as any).__environment = 'test';
}
Expand Down Expand Up @@ -386,6 +394,12 @@ const getIndexHTML = async () => {

let httpServer = server.listen(port);
httpServer.on('listening', () => {
let actualPort =
(httpServer.address() as import('net').AddressInfo | null)?.port ?? port;
writeRuntimeMetadata({
pid: process.pid,
port: actualPort,
});
if (isEnvironmentMode()) {
registerService(httpServer, serviceName, { wildcardSubdomains: true });
}
Expand Down
38 changes: 37 additions & 1 deletion packages/realm-server/middleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
const REQUEST_BODY_STATE = 'requestBody';

interface ProxyOptions {
requestHeaders?: Record<string, string>;
responseHeaders?: Record<string, string>;
}

Expand All @@ -33,6 +34,11 @@ export function proxyAsset(
return `/${filename}`;
},
events: {
proxyReq: (proxyReq) => {
for (let [key, value] of Object.entries(opts?.requestHeaders ?? {})) {
proxyReq.setHeader(key, value);
}
},
proxyRes: (_proxyRes, _req, res) => {
for (let [key, value] of Object.entries(opts?.responseHeaders ?? {})) {
res.setHeader(key, value);
Expand Down Expand Up @@ -102,10 +108,40 @@ export function ecsMetadata(ctxt: Koa.Context, next: Koa.Next) {
return next();
}

function isLoopbackAddress(address: string | undefined): boolean {
return (
address === '127.0.0.1' ||
address === '::1' ||
address === '::ffff:127.0.0.1'
);
}

export function fullRequestURL(ctxt: Koa.Context): URL {
let protocol =
ctxt.req.headers['x-forwarded-proto'] === 'https' ? 'https' : 'http';
return new URL(`${protocol}://${ctxt.req.headers.host}${ctxt.req.url}`);
let computedURL = new URL(
`${protocol}://${ctxt.req.headers.host}${ctxt.req.url}`,
);
let forwardedURL = ctxt.req.headers['x-boxel-forwarded-url'];
if (
process.env.BOXEL_TRUST_FORWARDED_URL === 'true' &&
typeof forwardedURL === 'string' &&
forwardedURL.trim() !== '' &&
isLoopbackAddress(ctxt.req.socket?.remoteAddress)
) {
try {
let parsed = new URL(forwardedURL);
if (
parsed.pathname === computedURL.pathname &&
parsed.search === computedURL.search
) {
return parsed;
}
} catch {
// Ignore malformed forwarded URLs and fall back to the computed request.
}
}
return computedURL;
}

export async function fetchRequestFromContext(
Expand Down
Loading
Loading