Files
orca/cloud/apps/relay/src/config.ts
Neil 340c917429 relay: edge export skip on outage, cohort-exit drain sweep, region-scoped account allow (#39707)
* relay: edge export skip on sustained outage, evacuation drain sweep, account allow scoped by region

- Leaving hosts skip the edge export once it has been unavailable for 120 s
  (ORCA_RELAY_EDGE_EXPORT_SKIP_AFTER_MS), keeping Postgres devices and
  fencing best effort, instead of 503ing until the edge is back.
- Directors drain edge hosts that left the cohort (POST /__admin/drain,
  20 per jittered 60 s tick) so connected desktops re-ask and move off.
- Account-allowlisted hosts must also be in the edge regions.
- Tests for the invite-socket edge branch.

* relay: export skip needs continuous failures; evacuation sweep runs soon after start, then every 10 min

Failures more than 30 s apart restart the unavailable clock, so one old failure cannot skip an
export. The sweep reads the edge rows (cell_id has no index): first pass 45-75 s after start (a
cohort change is a new revision), every 45-75 s while hosts are queued, else every 7.5-12.5 min.
2026-11-07 20:09:41 -08:00

504 lines
21 KiB
TypeScript

import { z } from 'zod'
import { isRelayCellConnectionHardCap, RELAY_CELL_CONNECTION_HARD_CAP, RELAY_MAX_CELL_CONNECTION_UNOBSERVED_BOUND, relayCellAdmissionBounds, type RelayCellConnectionHardCap } from '@orca-cloud/relay-contract/admission-budgets'
import { RELAY_DEFAULT_REGION, RelayRegionSchema, type RelayRegion } from '@orca-cloud/relay-contract/relay-regions'
import {
RELAY_MAX_READINESS_GRACE_MS,
RELAY_READINESS_JWKS_GRACE_MS,
RELAY_READINESS_SQL_GRACE_MS
} from './process-lifecycle/relay-readiness.js'
import type { EdgeRelayConfig } from './edge-relay.js'
export const RELAY_MAX_CELL_CAPACITY_REQUESTS = 100_000
export const RELAY_DATABASE_POOL_MAX = 10
export const RELAY_DIRECTOR_DATABASE_POOL_MAX = 3
export const RELAY_PUBLIC_RESOLVE_CONCURRENCY = 1
export const RELAY_PUBLIC_RESOLVE_WAIT_MS = 5_000
const RelayCellConnectionHardCapSchema = z.custom<RelayCellConnectionHardCap>(
isRelayCellConnectionHardCap
)
const EnvironmentBooleanSchema = z
.enum(['true', 'false'])
.default('true')
.transform((value) => value === 'true')
const OptionalServiceAccountSchema = z.preprocess(
(value) => (value === '' ? undefined : value),
z.string().email().optional()
)
// Cloud Run unset values arrive as empty strings, which coerce to 0 (fail-first readiness) instead of the grace default.
const readinessGraceSchema = (defaultMs: number) =>
z.preprocess(
(value) => (value === '' ? undefined : value),
z.coerce.number().int().min(0).max(RELAY_MAX_READINESS_GRACE_MS).default(defaultMs)
)
const EnvSchema = z.object({
PORT: z.coerce.number().int().positive().default(8080),
ORCA_RELAY_PUBLIC_URL: z.string().url(),
ORCA_RELAY_CELL_URL: z.string().url(),
ORCA_RELAY_AUTH_ISSUER: z.string().url(),
ORCA_RELAY_AUTH_AUDIENCE: z.literal('orca-relay').default('orca-relay'),
ORCA_RELAY_JWKS_URL: z.string().url(),
ORCA_RELAY_ASSIGNMENT_SIGNING_KEY: z.string().min(32),
ORCA_RELAY_ROLE: z.enum(['combined', 'director', 'cell']).default('combined'),
ORCA_RELAY_CELL_ID: z.string().min(1).max(128).default('combined'),
ORCA_RELAY_REGION: RelayRegionSchema.default(RELAY_DEFAULT_REGION),
ORCA_RELAY_CELL_CAPACITY: z.coerce
.number()
.int()
.positive()
.max(RELAY_MAX_CELL_CAPACITY_REQUESTS)
.default(900),
ORCA_RELAY_CELL_CONNECTION_HARD_CAP: z.coerce
.number()
.int()
.pipe(RelayCellConnectionHardCapSchema)
.optional(),
ORCA_RELAY_CELL_CONNECTION_UNOBSERVED_BOUND: z.coerce
.number()
.int()
.nonnegative()
.max(RELAY_MAX_CELL_CONNECTION_UNOBSERVED_BOUND)
.optional(),
ORCA_RELAY_CELLS_JSON: z.string().default('[]'),
ORCA_RELAY_ADMIN_AUDIENCE: z.string().url(),
ORCA_RELAY_DEPLOY_SERVICE_ACCOUNT: z.string().email(),
ORCA_RELAY_CAPACITY_SERVICE_ACCOUNT: OptionalServiceAccountSchema,
ORCA_RELAY_ASIA_PROOF_SERVICE_ACCOUNT: OptionalServiceAccountSchema,
ORCA_RELAY_MONITOR_SERVICE_ACCOUNT: OptionalServiceAccountSchema,
ORCA_RELAY_FENCE_SERVICE_ACCOUNT: OptionalServiceAccountSchema,
ORCA_RELAY_FENCE_BROKER_SERVICE_ACCOUNT: OptionalServiceAccountSchema,
ORCA_RELAY_REHOME_DIRECTOR_SERVICE_ACCOUNT: OptionalServiceAccountSchema,
ORCA_RELAY_REHOME_AUDIENCE: z.preprocess(
(value) => (value === '' ? undefined : value),
z.string().url().optional()
),
ORCA_RELAY_RUNTIME_SERVICE_ACCOUNT: z.string().email().optional(),
ORCA_RELAY_DIRECTOR_URL: z.string().url().optional(),
ORCA_RELAY_HEARTBEAT_AUDIENCE: z.string().url().optional(),
ORCA_RELAY_IMAGE_DIGEST: z
.string()
.regex(/^sha256:[a-f0-9]{64}$/)
.optional(),
ORCA_RELAY_ADMIN_JWKS_URL: z.string().url().default('https://www.googleapis.com/oauth2/v3/certs'),
ORCA_RELAY_DATABASE_POOL_MAX: z.coerce.number().int().positive().max(100).optional(),
ORCA_RELAY_READINESS_JWKS_GRACE_MS: readinessGraceSchema(RELAY_READINESS_JWKS_GRACE_MS),
ORCA_RELAY_READINESS_SQL_GRACE_MS: readinessGraceSchema(RELAY_READINESS_SQL_GRACE_MS),
ORCA_RELAY_PUBLIC_ASSIGNMENTS_ENABLED: EnvironmentBooleanSchema,
ORCA_RELAY_REGIONAL_PLACEMENT_ENABLED: EnvironmentBooleanSchema,
ORCA_RELAY_REGION_CORRECTION_COHORT_PERCENT: z.coerce.number().int().min(0).max(100).default(0),
// Step 5 on directors: `off` (default) is today's path; `dry-run` asks the picked cell to
// check a booking without making it, and logs the answer; `on` places on switched-on cells.
ORCA_RELAY_RESERVE_PLACEMENT: z.enum(['off', 'dry-run', 'on']).default('off'),
// Step 1 C: this share of hosts (by hash) on a reserve cell outside their region is booked
// in it on reconnect; 0 (default) is off.
ORCA_RELAY_RESERVE_REHOME_PERCENT: z.coerce.number().int().min(0).max(100).default(0),
// Step 3 shadow directory: unset or empty polls no cell; `all` or a comma list of cell ids.
ORCA_RELAY_SHADOW_SEAT_FEED_CELLS: z
.string()
.default('')
.transform((value) =>
value.trim() === 'all'
? ('all' as const)
: value
.split(',')
.map((cellId) => cellId.trim())
.filter((cellId) => cellId.length > 0)
),
ORCA_RELAY_PUBLIC_ASSIGNMENT_CONCURRENCY: z.coerce.number().int().positive().max(100).default(2),
ORCA_RELAY_PUBLIC_STICKY_CONCURRENCY: z.coerce.number().int().positive().max(100).default(1),
ORCA_RELAY_PUBLIC_STICKY_QUEUE_MAX: z.coerce.number().int().positive().max(4_096).default(64),
ORCA_RELAY_PUBLIC_STICKY_WAIT_MS: z.coerce.number().int().positive().max(30_000).default(2_000),
ORCA_RELAY_PUBLIC_STICKY_RETRY_AFTER_SECONDS: z.coerce
.number()
.int()
.positive()
.max(60)
.default(2),
// Borrowed from placement concurrency, which always keeps at least one permit.
ORCA_RELAY_DRAIN_RETURN_CONCURRENCY: z.coerce.number().int().positive().max(4).default(1),
ORCA_RELAY_DRAIN_RETURN_QUEUE_MAX: z.coerce.number().int().positive().max(64).default(4),
ORCA_RELAY_DRAIN_RETURN_WAIT_MS: z.coerce.number().int().positive().max(10_000).default(3_000),
// The desktop parser caps a Retry-After at 5 minutes; a larger value is truncated there.
ORCA_RELAY_DRAIN_RETURN_MAX_RETRY_AFTER_SECONDS: z.coerce
.number()
.int()
.min(2)
.max(300)
.default(300),
ORCA_RELAY_PUBLIC_ASSIGNMENT_QUEUE_MAX: z.coerce
.number()
.int()
.positive()
.max(4_096)
.default(128),
ORCA_RELAY_PUBLIC_ASSIGNMENT_WAIT_MS: z.coerce
.number()
.int()
.positive()
.max(30_000)
.default(4_000),
ORCA_RELAY_PUBLIC_ASSIGNMENT_RETRY_AFTER_SECONDS: z.coerce
.number()
.int()
.positive()
.max(300)
.default(5),
// Relay edge (director only). Unset URL: no edge cell, no override, today's behaviour.
ORCA_RELAY_EDGE_CELL_URL: z.preprocess(
(value) => (value === '' ? undefined : value),
z.string().url().optional()
),
ORCA_RELAY_EDGE_ROLLOUT_PERCENT: z.coerce.number().int().min(0).max(100).default(0),
ORCA_RELAY_EDGE_ALLOW_HOSTS: z.string().default(''),
ORCA_RELAY_EDGE_DENY_HOSTS: z.string().default(''),
// Comma-separated account ids (relay token `sub`); every host of these accounts joins.
ORCA_RELAY_EDGE_ALLOW_USERS: z.string().default(''),
ORCA_RELAY_EDGE_REGIONS: z.string().default(RELAY_DEFAULT_REGION),
// Set: the edge keeps host data in its objects, so moves push and export it (signed with this
// key, never the assignment lease key). Never unset while rows are on the edge.
ORCA_RELAY_EDGE_ADMIN_SIGNING_KEY: z.preprocess(
(value) => (value === '' ? undefined : value),
z.string().min(32).optional()
),
// Unset: EDGE_EXPORT_SKIP_AFTER_MS. How long the edge's export must keep failing before leaving
// hosts are placed without it (0: on the first failure).
ORCA_RELAY_EDGE_EXPORT_SKIP_AFTER_MS: z.preprocess(
(value) => (value === '' ? undefined : value),
z.coerce.number().int().min(0).optional()
),
DATABASE_URL: z.string().optional(),
ORCA_RELAY_DATA_DIR: z.string().default('./data/relay')
})
const RelayCellConfigSchema = z
.object({
id: z.string().min(1).max(128),
url: z.string().url(),
capacityRequests: z.number().int().positive().max(RELAY_MAX_CELL_CAPACITY_REQUESTS),
region: RelayRegionSchema.default(RELAY_DEFAULT_REGION),
initiallyEnabled: z.boolean().optional(),
connectionHardCap: RelayCellConnectionHardCapSchema.optional(),
connectionUnobservedBound: z
.number()
.int()
.nonnegative()
.max(RELAY_MAX_CELL_CONNECTION_UNOBSERVED_BOUND)
.optional()
})
.strict()
.superRefine((value, context) => {
if (
(value.connectionHardCap === undefined) !==
(value.connectionUnobservedBound === undefined)
) {
context.addIssue({
code: 'custom',
message: 'connection hard cap and unobserved bound must be configured together'
})
} else if (
value.connectionHardCap !== undefined &&
value.connectionUnobservedBound! >
relayCellAdmissionBounds(value.connectionHardCap).maxUnobservedBound
) {
context.addIssue({
code: 'custom',
path: ['connectionUnobservedBound'],
message: 'connection unobserved bound must leave ordinary admission capacity'
})
}
})
export type RelayCellConfig = Omit<z.infer<typeof RelayCellConfigSchema>, 'region'> & {
region?: RelayRegion
}
export type RelayConfig = {
port: number
publicUrl: string
cellUrl: string
authIssuer: string
authAudience: 'orca-relay'
jwksUrl: string
assignmentSigningKey: Uint8Array
role: 'combined' | 'director' | 'cell'
cellId: string
region?: RelayRegion
cells: RelayCellConfig[]
adminAudience: string
deployServiceAccount: string
capacityServiceAccount?: string
asiaProofServiceAccount?: string
monitorServiceAccount?: string
fenceServiceAccount?: string
fenceBrokerServiceAccount?: string
rehomeDirectorServiceAccount?: string
rehomeAudience?: string
shadowSeatFeedCells?: 'all' | string[]
reservePlacement?: 'off' | 'dry-run' | 'on'
reserveRehomePercent?: number
runtimeServiceAccount: string
directorUrl?: string
heartbeatAudience?: string
imageDigest?: string
connectionHardCap?: RelayCellConnectionHardCap
connectionUnobservedBound?: number
adminJwksUrl: string
databasePoolMax: number
readinessJwksGraceMs?: number
readinessSqlGraceMs?: number
publicAssignmentsEnabled: boolean
regionalPlacementEnabled?: boolean
regionCorrectionCohortPercent?: number
publicAssignmentConcurrency: number
publicAssignmentQueueMax: number
publicAssignmentWaitMs: number
publicResolveConcurrency: number
publicResolveWaitMs: number
publicAssignmentRetryAfterSeconds: number
publicStickyConcurrency?: number
publicStickyQueueMax?: number
publicStickyWaitMs?: number
publicStickyRetryAfterSeconds?: number
drainReturnConcurrency?: number
drainReturnQueueMax?: number
drainReturnWaitMs?: number
drainReturnMaxRetryAfterSeconds?: number
databaseUrl?: string
dataDir: string
edge?: EdgeRelayConfig
}
function canonicalOrigin(value: string, name: string): string {
const url = new URL(value)
if (url.origin !== value || url.pathname !== '/') throw new Error(`${name} must be an origin`)
const loopback = ['127.0.0.1', 'localhost', '::1', '[::1]'].includes(url.hostname)
if (url.protocol !== 'https:' && !(loopback && url.protocol === 'http:')) {
throw new Error(`${name} must use HTTPS outside loopback development`)
}
return value
}
export function loadRelayConfig(env: NodeJS.ProcessEnv = process.env): RelayConfig {
const parsed = EnvSchema.parse(env)
const adminServiceAccounts = [
parsed.ORCA_RELAY_DEPLOY_SERVICE_ACCOUNT,
parsed.ORCA_RELAY_CAPACITY_SERVICE_ACCOUNT,
parsed.ORCA_RELAY_ASIA_PROOF_SERVICE_ACCOUNT,
parsed.ORCA_RELAY_MONITOR_SERVICE_ACCOUNT,
parsed.ORCA_RELAY_FENCE_SERVICE_ACCOUNT,
parsed.ORCA_RELAY_FENCE_BROKER_SERVICE_ACCOUNT,
parsed.ORCA_RELAY_REHOME_DIRECTOR_SERVICE_ACCOUNT
].filter((value): value is string => value !== undefined)
if (new Set(adminServiceAccounts).size !== adminServiceAccounts.length) {
throw new Error('relay admin service accounts must be distinct')
}
if (
(parsed.ORCA_RELAY_REHOME_DIRECTOR_SERVICE_ACCOUNT === undefined) !==
(parsed.ORCA_RELAY_REHOME_AUDIENCE === undefined)
) {
throw new Error('relay rehome identity and audience must be configured together')
}
if (
parsed.ORCA_RELAY_REHOME_DIRECTOR_SERVICE_ACCOUNT &&
parsed.ORCA_RELAY_REHOME_DIRECTOR_SERVICE_ACCOUNT ===
(parsed.ORCA_RELAY_RUNTIME_SERVICE_ACCOUNT ?? parsed.ORCA_RELAY_DEPLOY_SERVICE_ACCOUNT)
) {
throw new Error('relay rehome director identity must differ from the cell runtime identity')
}
if (
parsed.ORCA_RELAY_REHOME_AUDIENCE &&
new URL(parsed.ORCA_RELAY_REHOME_AUDIENCE).pathname !== '/v1/admin/host-drain'
) {
throw new Error('relay rehome audience must target the host drain route')
}
const directorUrl = parsed.ORCA_RELAY_DIRECTOR_URL
? canonicalOrigin(parsed.ORCA_RELAY_DIRECTOR_URL, 'ORCA_RELAY_DIRECTOR_URL')
: undefined
const publicUrl = canonicalOrigin(parsed.ORCA_RELAY_PUBLIC_URL, 'ORCA_RELAY_PUBLIC_URL')
const configuredCells = z
.array(RelayCellConfigSchema)
.max(128)
.parse(JSON.parse(parsed.ORCA_RELAY_CELLS_JSON) as unknown)
.map((cell) => ({ ...cell, url: canonicalOrigin(cell.url, `cell ${cell.id}`) }))
const ownCell = {
id: parsed.ORCA_RELAY_CELL_ID,
region: parsed.ORCA_RELAY_REGION,
url: canonicalOrigin(parsed.ORCA_RELAY_CELL_URL, 'ORCA_RELAY_CELL_URL'),
capacityRequests: parsed.ORCA_RELAY_CELL_CAPACITY,
connectionHardCap: parsed.ORCA_RELAY_CELL_CONNECTION_HARD_CAP,
connectionUnobservedBound: parsed.ORCA_RELAY_CELL_CONNECTION_UNOBSERVED_BOUND,
initiallyEnabled: true
}
if (
(ownCell.connectionHardCap === undefined) !==
(ownCell.connectionUnobservedBound === undefined)
) {
throw new Error('connection hard cap and unobserved bound must be configured together')
}
if (
ownCell.connectionHardCap !== undefined &&
ownCell.connectionUnobservedBound! >
relayCellAdmissionBounds(ownCell.connectionHardCap).maxUnobservedBound
) {
throw new Error('connection unobserved bound must leave ordinary admission capacity')
}
const cells = parsed.ORCA_RELAY_ROLE === 'director' ? configuredCells : [ownCell]
if (cells.length === 0) throw new Error('director requires at least one configured cell')
if (new Set(cells.map(({ id }) => id)).size !== cells.length) {
throw new Error('relay cell ids must be unique')
}
const databasePoolMax =
parsed.ORCA_RELAY_DATABASE_POOL_MAX ??
(parsed.ORCA_RELAY_ROLE === 'director'
? RELAY_DIRECTOR_DATABASE_POOL_MAX
: RELAY_DATABASE_POOL_MAX)
if (
parsed.ORCA_RELAY_ROLE !== 'cell' &&
parsed.ORCA_RELAY_PUBLIC_ASSIGNMENT_CONCURRENCY >= databasePoolMax
) {
throw new Error('public relay admission must leave database pool headroom')
}
if (
parsed.ORCA_RELAY_ROLE !== 'cell' &&
parsed.ORCA_RELAY_PUBLIC_ASSIGNMENT_CONCURRENCY + parsed.ORCA_RELAY_PUBLIC_STICKY_CONCURRENCY >
databasePoolMax
) {
throw new Error('sticky and placement admission together must fit the database pool')
}
const edge = parseEdgeConfig(parsed, publicUrl)
if (edge && cells.some((cell) => cell.id === 'edge' || cell.url === edge.cellUrl)) {
throw new Error('the relay edge cell id and URL must not be a configured cell')
}
return {
port: parsed.PORT,
publicUrl,
cellUrl: ownCell.url,
authIssuer: canonicalOrigin(parsed.ORCA_RELAY_AUTH_ISSUER, 'ORCA_RELAY_AUTH_ISSUER'),
authAudience: parsed.ORCA_RELAY_AUTH_AUDIENCE,
jwksUrl: parsed.ORCA_RELAY_JWKS_URL,
assignmentSigningKey: new TextEncoder().encode(parsed.ORCA_RELAY_ASSIGNMENT_SIGNING_KEY),
role: parsed.ORCA_RELAY_ROLE,
cellId: ownCell.id,
region: ownCell.region,
cells,
adminAudience: parsed.ORCA_RELAY_ADMIN_AUDIENCE,
deployServiceAccount: parsed.ORCA_RELAY_DEPLOY_SERVICE_ACCOUNT,
capacityServiceAccount: parsed.ORCA_RELAY_CAPACITY_SERVICE_ACCOUNT,
asiaProofServiceAccount: parsed.ORCA_RELAY_ASIA_PROOF_SERVICE_ACCOUNT,
monitorServiceAccount: parsed.ORCA_RELAY_MONITOR_SERVICE_ACCOUNT,
fenceServiceAccount: parsed.ORCA_RELAY_FENCE_SERVICE_ACCOUNT,
fenceBrokerServiceAccount: parsed.ORCA_RELAY_FENCE_BROKER_SERVICE_ACCOUNT,
rehomeDirectorServiceAccount: parsed.ORCA_RELAY_REHOME_DIRECTOR_SERVICE_ACCOUNT,
rehomeAudience: parsed.ORCA_RELAY_REHOME_AUDIENCE,
shadowSeatFeedCells: parsed.ORCA_RELAY_SHADOW_SEAT_FEED_CELLS,
reservePlacement: parsed.ORCA_RELAY_RESERVE_PLACEMENT,
reserveRehomePercent: parsed.ORCA_RELAY_RESERVE_REHOME_PERCENT,
runtimeServiceAccount:
parsed.ORCA_RELAY_RUNTIME_SERVICE_ACCOUNT ?? parsed.ORCA_RELAY_DEPLOY_SERVICE_ACCOUNT,
directorUrl,
heartbeatAudience:
parsed.ORCA_RELAY_HEARTBEAT_AUDIENCE ??
(directorUrl || parsed.ORCA_RELAY_ROLE === 'director'
? new URL('/v1/admin/cell-heartbeat', directorUrl ?? publicUrl).toString()
: undefined),
imageDigest: parsed.ORCA_RELAY_IMAGE_DIGEST,
connectionHardCap: ownCell.connectionHardCap,
connectionUnobservedBound: ownCell.connectionUnobservedBound,
adminJwksUrl: parsed.ORCA_RELAY_ADMIN_JWKS_URL,
databasePoolMax,
readinessJwksGraceMs: parsed.ORCA_RELAY_READINESS_JWKS_GRACE_MS,
readinessSqlGraceMs: parsed.ORCA_RELAY_READINESS_SQL_GRACE_MS,
publicAssignmentsEnabled: parsed.ORCA_RELAY_PUBLIC_ASSIGNMENTS_ENABLED,
regionalPlacementEnabled: parsed.ORCA_RELAY_REGIONAL_PLACEMENT_ENABLED,
regionCorrectionCohortPercent: parsed.ORCA_RELAY_REGION_CORRECTION_COHORT_PERCENT,
publicAssignmentConcurrency: parsed.ORCA_RELAY_PUBLIC_ASSIGNMENT_CONCURRENCY,
publicAssignmentQueueMax: parsed.ORCA_RELAY_PUBLIC_ASSIGNMENT_QUEUE_MAX,
publicAssignmentWaitMs: parsed.ORCA_RELAY_PUBLIC_ASSIGNMENT_WAIT_MS,
publicResolveConcurrency: RELAY_PUBLIC_RESOLVE_CONCURRENCY,
publicResolveWaitMs: RELAY_PUBLIC_RESOLVE_WAIT_MS,
publicAssignmentRetryAfterSeconds: parsed.ORCA_RELAY_PUBLIC_ASSIGNMENT_RETRY_AFTER_SECONDS,
publicStickyConcurrency: parsed.ORCA_RELAY_PUBLIC_STICKY_CONCURRENCY,
publicStickyQueueMax: parsed.ORCA_RELAY_PUBLIC_STICKY_QUEUE_MAX,
publicStickyWaitMs: parsed.ORCA_RELAY_PUBLIC_STICKY_WAIT_MS,
publicStickyRetryAfterSeconds: parsed.ORCA_RELAY_PUBLIC_STICKY_RETRY_AFTER_SECONDS,
drainReturnConcurrency: parsed.ORCA_RELAY_DRAIN_RETURN_CONCURRENCY,
drainReturnQueueMax: parsed.ORCA_RELAY_DRAIN_RETURN_QUEUE_MAX,
drainReturnWaitMs: parsed.ORCA_RELAY_DRAIN_RETURN_WAIT_MS,
drainReturnMaxRetryAfterSeconds: parsed.ORCA_RELAY_DRAIN_RETURN_MAX_RETRY_AFTER_SECONDS,
databaseUrl: parsed.DATABASE_URL,
dataDir: parsed.ORCA_RELAY_DATA_DIR,
...(edge ? { edge } : {})
}
}
const RELAY_HOST_ID_PATTERN = /^[A-Za-z0-9_-]{16}$/
function hostIdList(value: string, name: string): Set<string> {
const ids = value
.split(',')
.map((id) => id.trim())
.filter((id) => id.length > 0)
for (const id of ids) {
if (!RELAY_HOST_ID_PATTERN.test(id)) {
throw new Error(`${name} has an invalid relay host id`)
}
}
return new Set(ids)
}
function userIdList(value: string): Set<string> {
const ids = value
.split(',')
.map((id) => id.trim())
.filter((id) => id.length > 0)
for (const id of ids) {
if (id.length > 128 || !/^[A-Za-z0-9_.:|-]+$/.test(id)) {
throw new Error('ORCA_RELAY_EDGE_ALLOW_USERS has an invalid account id')
}
}
return new Set(ids)
}
function parseEdgeConfig(
parsed: z.infer<typeof EnvSchema>,
publicUrl: string
): EdgeRelayConfig | undefined {
if (parsed.ORCA_RELAY_ROLE !== 'director' || !parsed.ORCA_RELAY_EDGE_CELL_URL) {
return undefined
}
const cellUrl = canonicalOrigin(parsed.ORCA_RELAY_EDGE_CELL_URL, 'ORCA_RELAY_EDGE_CELL_URL')
if (cellUrl === publicUrl) {
throw new Error('ORCA_RELAY_EDGE_CELL_URL must differ from the director')
}
const regions = parsed.ORCA_RELAY_EDGE_REGIONS.split(',')
.map((region) => region.trim())
.filter((region) => region.length > 0)
.map((region) => RelayRegionSchema.parse(region))
const adminKey = parsed.ORCA_RELAY_EDGE_ADMIN_SIGNING_KEY
// The deployed edge keeps host data in its objects: without the key the director could not
// push it, and hosts would land on an edge that holds none of their devices.
if (adminKey === undefined) {
throw new Error('ORCA_RELAY_EDGE_CELL_URL needs ORCA_RELAY_EDGE_ADMIN_SIGNING_KEY')
}
if (adminKey === parsed.ORCA_RELAY_ASSIGNMENT_SIGNING_KEY) {
throw new Error('ORCA_RELAY_EDGE_ADMIN_SIGNING_KEY must differ from the assignment signing key')
}
return {
cellUrl,
adminSigningKey: new TextEncoder().encode(adminKey),
rolloutPercent: parsed.ORCA_RELAY_EDGE_ROLLOUT_PERCENT,
allowHosts: hostIdList(parsed.ORCA_RELAY_EDGE_ALLOW_HOSTS, 'ORCA_RELAY_EDGE_ALLOW_HOSTS'),
allowUsers: userIdList(parsed.ORCA_RELAY_EDGE_ALLOW_USERS),
denyHosts: hostIdList(parsed.ORCA_RELAY_EDGE_DENY_HOSTS, 'ORCA_RELAY_EDGE_DENY_HOSTS'),
regions: new Set(regions),
...(parsed.ORCA_RELAY_EDGE_EXPORT_SKIP_AFTER_MS === undefined
? {}
: { exportSkipAfterMs: parsed.ORCA_RELAY_EDGE_EXPORT_SKIP_AFTER_MS })
}
}