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
16 changes: 16 additions & 0 deletions docker/docker-compose.community.yml
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ services:
ENCRYPTION_SECRET: '${HIVE_ENCRYPTION_SECRET}'
WEB_APP_URL: '${HIVE_APP_BASE_URL}'
PORT: 3001
SERVER_HOST: '${SERVER_HOST:-::}'
SERVER_HOST_IPV6_ONLY: '${SERVER_HOST_IPV6_ONLY:-0}'
S3_ENDPOINT: 'http://s3:9000'
S3_ACCESS_KEY_ID: ${MINIO_ROOT_USER}
S3_SECRET_ACCESS_KEY: ${MINIO_ROOT_PASSWORD}
Expand Down Expand Up @@ -234,6 +236,8 @@ services:
environment:
NODE_ENV: production
PORT: 3012
SERVER_HOST: '${SERVER_HOST:-::}'
SERVER_HOST_IPV6_ONLY: '${SERVER_HOST_IPV6_ONLY:-0}'
LOG_LEVEL: '${LOG_LEVEL:-debug}'
OPENTELEMETRY_COLLECTOR_ENDPOINT: '${OPENTELEMETRY_COLLECTOR_ENDPOINT:-}'
SENTRY: '${SENTRY:-0}'
Expand All @@ -250,6 +254,8 @@ services:
environment:
NODE_ENV: production
PORT: 3002
SERVER_HOST: '${SERVER_HOST:-::}'
SERVER_HOST_IPV6_ONLY: '${SERVER_HOST_IPV6_ONLY:-0}'
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: '${REDIS_PASSWORD}'
Expand Down Expand Up @@ -278,6 +284,8 @@ services:
REDIS_PORT: 6379
REDIS_PASSWORD: '${REDIS_PASSWORD}'
PORT: 3003
SERVER_HOST: '${SERVER_HOST:-::}'
SERVER_HOST_IPV6_ONLY: '${SERVER_HOST_IPV6_ONLY:-0}'
LOG_LEVEL: '${LOG_LEVEL:-debug}'
OPENTELEMETRY_COLLECTOR_ENDPOINT: '${OPENTELEMETRY_COLLECTOR_ENDPOINT:-}'
SENTRY: '${SENTRY:-0}'
Expand All @@ -294,6 +302,8 @@ services:
environment:
NODE_ENV: production
PORT: 3014
SERVER_HOST: '${SERVER_HOST:-::}'
SERVER_HOST_IPV6_ONLY: '${SERVER_HOST_IPV6_ONLY:-0}'
POSTGRES_HOST: db
POSTGRES_PORT: 5432
POSTGRES_DB: '${POSTGRES_DB}'
Expand Down Expand Up @@ -340,6 +350,8 @@ services:
REDIS_PORT: 6379
REDIS_PASSWORD: '${REDIS_PASSWORD}'
PORT: 3006
SERVER_HOST: '${SERVER_HOST:-::}'
SERVER_HOST_IPV6_ONLY: '${SERVER_HOST_IPV6_ONLY:-0}'
LOG_LEVEL: '${LOG_LEVEL:-debug}'
SENTRY: '${SENTRY:-0}'
SENTRY_DSN: '${SENTRY_DSN:-}'
Expand Down Expand Up @@ -367,6 +379,8 @@ services:
CLICKHOUSE_USERNAME: '${CLICKHOUSE_USER}'
CLICKHOUSE_PASSWORD: '${CLICKHOUSE_PASSWORD}'
PORT: 3007
SERVER_HOST: '${SERVER_HOST:-::}'
SERVER_HOST_IPV6_ONLY: '${SERVER_HOST_IPV6_ONLY:-0}'
LOG_LEVEL: '${LOG_LEVEL:-debug}'
SENTRY: '${SENTRY:-0}'
SENTRY_DSN: '${SENTRY_DSN:-}'
Expand All @@ -380,6 +394,8 @@ services:
- 'stack'
environment:
PORT: 3000
SERVER_HOST: '${SERVER_HOST:-::}'
SERVER_HOST_IPV6_ONLY: '${SERVER_HOST_IPV6_ONLY:-0}'
NODE_ENV: production
APP_BASE_URL: '${HIVE_APP_BASE_URL}'
GRAPHQL_PUBLIC_ENDPOINT: http://localhost:8082/graphql
Expand Down
4 changes: 4 additions & 0 deletions packages/services/broker-worker/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CF_BROKER_SIGNATURE=dev-secret
PORT=4010
SERVER_HOST=::
SERVER_HOST_IPV6_ONLY=0
1 change: 1 addition & 0 deletions packages/services/broker-worker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
},
"devDependencies": {
"@cloudflare/workers-types": "4.20250913.0",
"@hive/service-common": "workspace:*",
"@types/service-worker-mock": "2.0.4",
"@whatwg-node/server": "0.10.17",
"esbuild": "0.25.9",
Expand Down
16 changes: 15 additions & 1 deletion packages/services/broker-worker/src/dev.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { createServer } from 'http';
import { Router } from 'itty-router';
import { resolveServerListenOptions } from '@hive/service-common/listen-options';
import { createServerAdapter } from '@whatwg-node/server';
import { createSignatureValidator } from './auth';
import { env } from './dev-polyfill';
import { handleRequest } from './handler';

// eslint-disable-next-line no-process-env
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 4010;
const listenOptions = resolveServerListenOptions({
// eslint-disable-next-line no-process-env
serverHost: process.env.SERVER_HOST,
// eslint-disable-next-line no-process-env
serverHostIpv6Only: process.env.SERVER_HOST_IPV6_ONLY === '1' ? '1' : '0',
});
const isSignatureValid = createSignatureValidator(env.SIGNATURE);

function main() {
Expand All @@ -32,7 +39,14 @@ function main() {
const server = createServer(app);

return new Promise<void>(resolve => {
server.listen(PORT, '::', resolve);
server.listen(
{
port: PORT,
host: listenOptions.host,
ipv6Only: listenOptions.ipv6Only,
},
resolve,
);
});
}

Expand Down
5 changes: 4 additions & 1 deletion packages/services/cdn-worker/.env.template
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
S3_ENDPOINT=http://localhost:9000
S3_ACCESS_KEY_ID="minioadmin"
S3_SECRET_ACCESS_KEY="minioadmin"
S3_BUCKET_NAME="artifacts"
S3_BUCKET_NAME="artifacts"
PORT=4010
SERVER_HOST=::
SERVER_HOST_IPV6_ONLY=0
1 change: 1 addition & 0 deletions packages/services/cdn-worker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
},
"devDependencies": {
"@cloudflare/workers-types": "4.20250913.0",
"@hive/service-common": "workspace:*",
"@types/service-worker-mock": "2.0.4",
"@whatwg-node/server": "0.10.17",
"bcryptjs": "2.4.3",
Expand Down
16 changes: 15 additions & 1 deletion packages/services/cdn-worker/src/dev.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createServer } from 'http';
import * as itty from 'itty-router';
import { resolveServerListenOptions } from '@hive/service-common/listen-options';
import { createServerAdapter } from '@whatwg-node/server';
import { createArtifactRequestHandler } from './artifact-handler';
import { ArtifactStorageReader } from './artifact-storage-reader';
Expand All @@ -22,6 +23,12 @@ const s3 = {

// eslint-disable-next-line no-process-env
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 4010;
const listenOptions = resolveServerListenOptions({
// eslint-disable-next-line no-process-env
serverHost: process.env.SERVER_HOST,
// eslint-disable-next-line no-process-env
serverHostIpv6Only: process.env.SERVER_HOST_IPV6_ONLY === '1' ? '1' : '0',
});

const artifactStorageReader = new ArtifactStorageReader(s3, null, null, null);

Expand Down Expand Up @@ -93,7 +100,14 @@ function main() {
const server = createServer(app);

return new Promise<void>(resolve => {
server.listen(PORT, '::', resolve);
server.listen(
{
port: PORT,
host: listenOptions.host,
ipv6Only: listenOptions.ipv6Only,
},
resolve,
);
});
}

Expand Down
2 changes: 2 additions & 0 deletions packages/services/commerce/.env.template
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
PORT=4013
SERVER_HOST=::
SERVER_HOST_IPV6_ONLY=0
OPENTELEMETRY_COLLECTOR_ENDPOINT="<sync>"
CLICKHOUSE_PROTOCOL="http"
CLICKHOUSE_HOST="localhost"
Expand Down
8 changes: 7 additions & 1 deletion packages/services/commerce/src/environment.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import zod from 'zod';
import { OpenTelemetryConfigurationModel } from '@hive/service-common';
import { OpenTelemetryConfigurationModel, resolveServerListenOptions } from '@hive/service-common';

const isNumberString = (input: unknown) => zod.string().regex(/^\d+$/).safeParse(input).success;

Expand All @@ -22,6 +22,8 @@ export const emptyString = <T extends zod.ZodType>(input: T) => {

const EnvironmentModel = zod.object({
PORT: emptyString(NumberFromString.optional()),
SERVER_HOST: emptyString(zod.string().optional()),
SERVER_HOST_IPV6_ONLY: emptyString(zod.union([zod.literal('1'), zod.literal('0')]).optional()),
ENVIRONMENT: emptyString(zod.string().optional()),
RELEASE: emptyString(zod.string().optional()),
});
Expand Down Expand Up @@ -141,6 +143,10 @@ export const env = {
release: base.RELEASE ?? 'local',
http: {
port: base.PORT ?? 4012,
...resolveServerListenOptions({
serverHost: base.SERVER_HOST,
serverHostIpv6Only: base.SERVER_HOST_IPV6_ONLY,
}),
},
tracing: {
enabled: !!tracing.OPENTELEMETRY_COLLECTOR_ENDPOINT,
Expand Down
9 changes: 7 additions & 2 deletions packages/services/commerce/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,16 @@ async function main() {
});

if (env.prometheus) {
await startMetrics(env.prometheus.labels.instance, env.prometheus.port);
await startMetrics(env.prometheus.labels.instance, {
port: env.prometheus.port,
host: env.http.host,
ipv6Only: env.http.ipv6Only,
});
}
await server.listen({
port: env.http.port,
host: '::',
host: env.http.host,
ipv6Only: env.http.ipv6Only,
});
await Promise.all([usageEstimator.start(), rateLimiter.start(), stripeBilling.start()]);
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
SECRET=secretsecret
SECRET=secretsecret
PORT=3069
SERVER_HOST=::
SERVER_HOST_IPV6_ONLY=0
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"@apollo/composition": "2.13.2",
"@apollo/federation-internals": "2.13.2",
"@graphql-hive/external-composition": "workspace:*",
"@hive/service-common": "workspace:*",
"@whatwg-node/server": "0.10.17",
"dotenv": "16.4.7",
"graphql": "16.9.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { resolveEnv } from './environment';

describe('resolveEnv', () => {
// eslint-disable-next-line no-restricted-syntax -- explicit IPv4 literal required for validation coverage
const ipv4Host = '0.0.0.0';

test('uses host and ipv6 defaults when not provided', () => {
const env = resolveEnv({
SECRET: 'secretsecret',
});

expect(env.http).toEqual({
port: 3069,
host: '::',
ipv6Only: false,
});
});

test('honors explicit host and ipv6-only values', () => {
const env = resolveEnv({
SECRET: 'secretsecret',
PORT: '4000',
SERVER_HOST: '::1',
SERVER_HOST_IPV6_ONLY: '1',
});

expect(env.http).toEqual({
port: 4000,
host: '::1',
ipv6Only: true,
});
});

test('rejects ipv6-only combined with an IPv4 literal host', () => {
expect(() =>
resolveEnv({
SECRET: 'secretsecret',
SERVER_HOST: ipv4Host,
SERVER_HOST_IPV6_ONLY: '1',
}),
).toThrow(/SERVER_HOST_IPV6_ONLY=1 is incompatible with IPv4 host "0\.0\.0\.0"/);
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import zod from 'zod';
import { resolveServerListenOptions } from '@hive/service-common';

function extractConfig<Input, Output>(config: zod.SafeParseReturnType<Input, Output>): Output {
if (!config.success) {
Expand All @@ -15,6 +16,8 @@ const BaseSchema = zod.object({
.number()
.transform(port => port || 3069)
.default(3069),
SERVER_HOST: zod.string().default('::'),
SERVER_HOST_IPV6_ONLY: zod.union([zod.literal('1'), zod.literal('0')]).default('0'),
SECRET: zod.string(),
});

Expand Down Expand Up @@ -44,6 +47,10 @@ export function resolveEnv(env: Record<string, string | undefined>) {
release: base.RELEASE ?? 'local',
http: {
port: base.PORT,
...resolveServerListenOptions({
serverHost: base.SERVER_HOST,
serverHostIpv6Only: base.SERVER_HOST_IPV6_ONLY,
}),
},
secret: base.SECRET,
};
Expand Down
17 changes: 14 additions & 3 deletions packages/services/external-composition/federation-2/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,20 @@ import { createRequestListener } from './server';
const env = resolveEnv(process.env);
const server = createServer(createRequestListener(env));

server.listen(env.http.port, '::', () => {
console.log(`Listening on http://localhost:${env.http.port}`);
});
function formatListenAddress(host: string, port: number) {
return host.includes(':') ? `[${host}]:${port}` : `${host}:${port}`;
}

server.listen(
{
port: env.http.port,
host: env.http.host,
ipv6Only: env.http.ipv6Only,
},
() => {
console.log(`Listening on ${formatListenAddress(env.http.host, env.http.port)}`);
},
);

process.on('SIGINT', () => {
server.close(err => {
Expand Down
2 changes: 2 additions & 0 deletions packages/services/policy/.env.template
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
ENVIRONMENT=development
LOG_LEVEL=debug
SERVER_HOST=::
SERVER_HOST_IPV6_ONLY=0
OPENTELEMETRY_COLLECTOR_ENDPOINT="<sync>"
8 changes: 7 additions & 1 deletion packages/services/policy/src/environment.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import zod from 'zod';
import { OpenTelemetryConfigurationModel } from '@hive/service-common';
import { OpenTelemetryConfigurationModel, resolveServerListenOptions } from '@hive/service-common';

const isNumberString = (input: unknown) => zod.string().regex(/^\d+$/).safeParse(input).success;

Expand All @@ -20,6 +20,8 @@ const emptyString = <T extends zod.ZodType>(input: T) => {

const EnvironmentModel = zod.object({
PORT: emptyString(NumberFromString.optional()),
SERVER_HOST: emptyString(zod.string().optional()),
SERVER_HOST_IPV6_ONLY: emptyString(zod.union([zod.literal('1'), zod.literal('0')]).optional()),
ENVIRONMENT: emptyString(zod.string().optional()),
RELEASE: emptyString(zod.string().optional()),
});
Expand Down Expand Up @@ -107,6 +109,10 @@ export const env = {
},
http: {
port: base.PORT ?? 6600,
...resolveServerListenOptions({
serverHost: base.SERVER_HOST,
serverHostIpv6Only: base.SERVER_HOST_IPV6_ONLY,
}),
},
sentry: sentry.SENTRY === '1' ? { dsn: sentry.SENTRY_DSN } : null,
log: {
Expand Down
9 changes: 7 additions & 2 deletions packages/services/policy/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,16 @@ async function main() {

await server.listen({
port: env.http.port,
host: '::',
host: env.http.host,
ipv6Only: env.http.ipv6Only,
});

if (env.prometheus) {
await startMetrics(env.prometheus.labels.instance, env.prometheus.port);
await startMetrics(env.prometheus.labels.instance, {
port: env.prometheus.port,
host: env.http.host,
ipv6Only: env.http.ipv6Only,
});
}
} catch (error) {
server.log.fatal(error);
Expand Down
2 changes: 2 additions & 0 deletions packages/services/schema/.env.template
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
REDIS_HOST="localhost"
REDIS_PORT="6379"
REDIS_PASSWORD=""
SERVER_HOST=::
SERVER_HOST_IPV6_ONLY=0
ENCRYPTION_SECRET="97e4094d2463e71a981913cca4e56788"
SCHEMA_CACHE_TTL_MS=5000
SCHEMA_CACHE_SUCCESS_TTL_MS=5000
Expand Down
Loading