Skip to content

Commit 3227b56

Browse files
Support environment overrides in entrypoint.sh
1 parent 1eeef72 commit 3227b56

File tree

11 files changed

+126
-38
lines changed

11 files changed

+126
-38
lines changed

Dockerfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,6 @@ ENV DATA_DIR=/data
173173
ENV DATA_CACHE_DIR=$DATA_DIR/.sourcebot
174174
ENV DATABASE_DATA_DIR=$DATA_CACHE_DIR/db
175175
ENV REDIS_DATA_DIR=$DATA_CACHE_DIR/redis
176-
ENV REDIS_URL="redis://localhost:6379"
177176
ENV SRC_TENANT_ENFORCEMENT_MODE=strict
178177
ENV SOURCEBOT_PUBLIC_KEY_PATH=/app/public.pem
179178

entrypoint.sh

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,57 @@
11
#!/bin/sh
2-
set -e
32

4-
# Check if DATABASE_URL is not set
5-
if [ -z "$DATABASE_URL" ]; then
6-
# Check if the individual database variables are set and construct the URL
7-
if [ -n "$DATABASE_HOST" ] && [ -n "$DATABASE_USERNAME" ] && [ -n "$DATABASE_PASSWORD" ] && [ -n "$DATABASE_NAME" ]; then
8-
DATABASE_URL="postgresql://${DATABASE_USERNAME}:${DATABASE_PASSWORD}@${DATABASE_HOST}/${DATABASE_NAME}"
3+
# Exit immediately if a command fails
4+
set -e
5+
# Disable auto-exporting of variables
6+
set +a
7+
8+
# If a CONFIG_PATH is set, resolve the environment overrides from the config file.
9+
# The overrides will be written into variables scopped to the current shell. This is
10+
# required in case one of the variables used in this entrypoint is overriden (e.g.,
11+
# DATABASE_URL, REDIS_URL, etc.)
12+
if [ -n "$CONFIG_PATH" ]; then
13+
echo -e "\e[34m[Info] Resolving environment overrides from $CONFIG_PATH...\e[0m"
14+
15+
set +e # Disable exist on error so we can capture EXIT_CODE
16+
OVERRIDES_OUTPUT=$(SKIP_ENV_VALIDATION=1 yarn tool:resolve-env-overrides 2>&1)
17+
EXIT_CODE=$?
18+
set -e # Re-enable exit on error
19+
20+
if [ $EXIT_CODE -eq 0 ]; then
21+
eval "$OVERRIDES_OUTPUT"
22+
else
23+
echo -e "\e[31m[Error] Failed to resolve environment overrides.\e[0m"
24+
echo "$OVERRIDES_OUTPUT"
25+
exit 1
26+
fi
27+
fi
928

10-
if [ -n "$DATABASE_ARGS" ]; then
11-
DATABASE_URL="${DATABASE_URL}?$DATABASE_ARGS"
12-
fi
29+
# Descontruct the database URL from the individual variables if DATABASE_URL is not set
30+
if [ -z "$DATABASE_URL" ] && [ -n "$DATABASE_HOST" ] && [ -n "$DATABASE_USERNAME" ] && [ -n "$DATABASE_PASSWORD" ] && [ -n "$DATABASE_NAME" ]; then
31+
DATABASE_URL="postgresql://${DATABASE_USERNAME}:${DATABASE_PASSWORD}@${DATABASE_HOST}/${DATABASE_NAME}"
1332

14-
export DATABASE_URL
15-
else
16-
# Otherwise, fallback to a default value
17-
DATABASE_URL="postgresql://postgres@localhost:5432/sourcebot"
18-
export DATABASE_URL
33+
if [ -n "$DATABASE_ARGS" ]; then
34+
DATABASE_URL="${DATABASE_URL}?$DATABASE_ARGS"
1935
fi
2036
fi
2137

22-
if [ "$DATABASE_URL" = "postgresql://postgres@localhost:5432/sourcebot" ]; then
23-
DATABASE_EMBEDDED="true"
38+
if [ -z "$DATABASE_URL" ]; then
39+
echo -e "\e[34m[Info] DATABASE_URL is not set. Using embeded database.\e[0m"
40+
export DATABASE_EMBEDDED="true"
41+
export DATABASE_URL="postgresql://postgres@localhost:5432/sourcebot"
42+
else
43+
export DATABASE_EMBEDDED="false"
44+
fi
45+
46+
if [ -z "$REDIS_URL" ]; then
47+
echo -e "\e[34m[Info] REDIS_URL is not set. Using embeded redis.\e[0m"
48+
export REDIS_EMBEDDED="true"
49+
export REDIS_URL="redis://localhost:6379"
50+
else
51+
export REDIS_EMBEDDED="false"
2452
fi
2553

54+
2655
echo -e "\e[34m[Info] Sourcebot version: $NEXT_PUBLIC_SOURCEBOT_VERSION\e[0m"
2756

2857
# If we don't have a PostHog key, then we need to disable telemetry.
@@ -59,7 +88,7 @@ if [ "$DATABASE_EMBEDDED" = "true" ] && [ ! -d "$DATABASE_DATA_DIR" ]; then
5988
fi
6089

6190
# Create the redis data directory if it doesn't exist
62-
if [ ! -d "$REDIS_DATA_DIR" ]; then
91+
if [ "$REDIS_EMBEDDED" = "true" ] && [ ! -d "$REDIS_DATA_DIR" ]; then
6392
mkdir -p $REDIS_DATA_DIR
6493
fi
6594

@@ -149,7 +178,6 @@ fi
149178

150179
echo "{\"version\": \"$NEXT_PUBLIC_SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL_ID\"}" > "$FIRST_RUN_FILE"
151180

152-
153181
# Start the database and wait for it to be ready before starting any other service
154182
if [ "$DATABASE_EMBEDDED" = "true" ]; then
155183
su postgres -c "postgres -D $DATABASE_DATA_DIR" &
@@ -171,7 +199,7 @@ fi
171199

172200
# Run a Database migration
173201
echo -e "\e[34m[Info] Running database migration...\e[0m"
174-
yarn workspace @sourcebot/db prisma:migrate:prod
202+
DATABASE_URL="$DATABASE_URL" yarn workspace @sourcebot/db prisma:migrate:prod
175203

176204
# Create the log directory
177205
mkdir -p /var/log/sourcebot

packages/backend/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import "./instrument.js";
22

33
import { PrismaClient } from "@sourcebot/db";
44
import { createLogger } from "@sourcebot/shared";
5-
import { env, getConfigSettings, hasEntitlement } from '@sourcebot/shared';
5+
import { env, getConfigSettings, hasEntitlement, getDBConnectionString } from '@sourcebot/shared';
66
import { existsSync } from 'fs';
77
import { mkdir } from 'fs/promises';
88
import { Redis } from 'ioredis';
@@ -31,7 +31,7 @@ if (!existsSync(indexPath)) {
3131
const prisma = new PrismaClient({
3232
datasources: {
3333
db: {
34-
url: env.DATABASE_URL,
34+
url: getDBConnectionString(),
3535
},
3636
},
3737
});

packages/shared/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"scripts": {
77
"build": "tsc",
88
"build:watch": "tsc-watch --preserveWatchOutput",
9-
"postinstall": "yarn build"
9+
"postinstall": "yarn build",
10+
"tool:resolve-env-overrides": "tsx tools/resolveEnvOverrides.ts"
1011
},
1112
"dependencies": {
1213
"@google-cloud/secret-manager": "^6.1.1",
@@ -26,6 +27,7 @@
2627
"@types/micromatch": "^4.0.9",
2728
"@types/node": "^22.7.5",
2829
"tsc-watch": "6.2.1",
30+
"tsx": "^4.19.1",
2931
"typescript": "^5.7.3"
3032
},
3133
"exports": {

packages/shared/src/db.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { env } from "./env.server.js";
2+
3+
export const getDBConnectionString = (): string | undefined => {
4+
if (env.DATABASE_URL) {
5+
return env.DATABASE_URL;
6+
}
7+
else if (env.DATABASE_HOST && env.DATABASE_USERNAME && env.DATABASE_PASSWORD && env.DATABASE_NAME) {
8+
let databaseUrl = `postgresql://${env.DATABASE_USERNAME}:${env.DATABASE_PASSWORD}@${env.DATABASE_HOST}/${env.DATABASE_NAME}`;
9+
if (env.DATABASE_ARGS) {
10+
databaseUrl += `?${env.DATABASE_ARGS}`;
11+
}
12+
13+
return databaseUrl;
14+
}
15+
}

packages/shared/src/env.server.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { createEnv } from "@t3-oss/env-core";
22
import { z } from "zod";
3-
import { SourcebotConfig } from "@sourcebot/schemas/v3/index.type";
4-
import { getTokenFromConfig } from "./crypto.js";
53
import { loadConfig } from "./utils.js";
64
import { tenancyModeSchema } from "./types.js";
5+
import { SourcebotConfig } from "@sourcebot/schemas/v3/index.type";
6+
import { getTokenFromConfig } from "./crypto.js";
77

88
// Booleans are specified as 'true' or 'false' strings.
99
const booleanSchema = z.enum(["true", "false"]);
@@ -13,16 +13,14 @@ const booleanSchema = z.enum(["true", "false"]);
1313
// @see: https://zod.dev/?id=coercion-for-primitives
1414
const numberSchema = z.coerce.number();
1515

16-
17-
const resolveEnvironmentVariableOverridesFromConfig = async (config: SourcebotConfig): Promise<Record<string, string>> => {
16+
export const resolveEnvironmentVariableOverridesFromConfig = async (config: SourcebotConfig): Promise<Record<string, string>> => {
1817
if (!config.environmentOverrides) {
1918
return {};
2019
}
2120

2221
const resolved: Record<string, string> = {};
2322

2423
const start = performance.now();
25-
console.debug('resolving environment variable overrides');
2624

2725
for (const [key, override] of Object.entries(config.environmentOverrides)) {
2826
switch (override.type) {
@@ -122,7 +120,16 @@ export const env = createEnv({
122120
CONFIG_MAX_REPOS_NO_TOKEN: numberSchema.default(Number.MAX_SAFE_INTEGER),
123121
NODE_ENV: z.enum(["development", "test", "production"]),
124122
SOURCEBOT_TELEMETRY_DISABLED: booleanSchema.default('false'),
125-
DATABASE_URL: z.string().url(),
123+
124+
// Database variables
125+
// Either DATABASE_URL or DATABASE_HOST, DATABASE_USERNAME, DATABASE_PASSWORD, and DATABASE_NAME must be set.
126+
// @see: shared/src/db.ts for more details.
127+
DATABASE_URL: z.string().url().optional(),
128+
DATABASE_HOST: z.string().optional(),
129+
DATABASE_USERNAME: z.string().optional(),
130+
DATABASE_PASSWORD: z.string().optional(),
131+
DATABASE_NAME: z.string().optional(),
132+
DATABASE_ARGS: z.string().optional(),
126133

127134
SOURCEBOT_TENANCY_MODE: tenancyModeSchema.default("single"),
128135
CONFIG_PATH: z.string(),

packages/shared/src/index.server.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ export {
2727
} from "./utils.js";
2828
export * from "./constants.js";
2929
export {
30-
env
30+
env,
31+
resolveEnvironmentVariableOverridesFromConfig,
3132
} from "./env.server.js";
3233
export {
3334
createLogger,
@@ -42,4 +43,7 @@ export {
4243
hashSecret,
4344
generateApiKey,
4445
verifySignature,
45-
} from "./crypto.js";
46+
} from "./crypto.js";
47+
export {
48+
getDBConnectionString,
49+
} from "./db.js";
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// The following script loads the config.json file and resolves any environment variable overrides.
2+
// It then writes then to stdout in the format of `KEY="VALUE"`.
3+
// This is used by entrypoint.sh to set them as variables.
4+
(async () => {
5+
if (!process.env.CONFIG_PATH) {
6+
console.error('CONFIG_PATH is not set');
7+
process.exit(1);
8+
}
9+
10+
// Silence all console logs so we don't pollute stdout.
11+
const originalConsoleLog = console.log;
12+
console.log = () => {};
13+
console.debug = () => {};
14+
console.info = () => {};
15+
console.warn = () => {};
16+
// console.error = () => {}; // Keep errors
17+
18+
const { loadConfig } = await import("../src/utils.js");
19+
const { resolveEnvironmentVariableOverridesFromConfig } = await import("../src/env.server.js");
20+
21+
const config = await loadConfig(process.env.CONFIG_PATH);
22+
const overrides = await resolveEnvironmentVariableOverridesFromConfig(config);
23+
24+
for (const [key, value] of Object.entries(overrides)) {
25+
const escapedValue = value.replace(/"/g, '\\"');
26+
originalConsoleLog(`${key}="${escapedValue}"`);
27+
}
28+
29+
process.exit(0);
30+
})();

packages/web/src/prisma.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import 'server-only';
2-
import { env } from "@sourcebot/shared";
2+
import { env, getDBConnectionString } from "@sourcebot/shared";
33
import { Prisma, PrismaClient } from "@sourcebot/db";
44
import { hasEntitlement } from "@sourcebot/shared";
55

66
// @see: https://authjs.dev/getting-started/adapters/prisma
77
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }
88

9+
const dbConnectionString = getDBConnectionString();
10+
911
// @NOTE: In almost all cases, the userScopedPrismaClientExtension should be used
1012
// (since actions & queries are scoped to a particular user). There are some exceptions
1113
// (e.g., in initialize.ts).
@@ -14,15 +16,15 @@ const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }
1416
// all of the actions & queries to use the userScopedPrismaClientExtension to avoid
1517
// accidental misuse.
1618
export const prisma = globalForPrisma.prisma || new PrismaClient({
17-
// @note: even though DATABASE_URL is of type string, we need to check if it's defined
18-
// because this code will be executed at build time, and env.DATABASE_URL will be undefined.
19-
...(env.DATABASE_URL ? {
19+
// @note: this code is evaluated at build time, and will throw exceptions if these env vars are not set.
20+
// Here we explicitly check if the DATABASE_URL or the individual database variables are set, and only
21+
...(dbConnectionString !== undefined ? {
2022
datasources: {
2123
db: {
22-
url: env.DATABASE_URL,
24+
url: dbConnectionString,
2325
},
2426
}
25-
} : {})
27+
}: {}),
2628
})
2729
if (env.NODE_ENV !== "production") globalForPrisma.prisma = prisma
2830

supervisord.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ redirect_stderr=true
3636
[program:redis]
3737
command=redis-server --dir %(ENV_REDIS_DATA_DIR)s
3838
priority=10
39-
autostart=true
39+
autostart=%(ENV_REDIS_EMBEDDED)s
4040
autorestart=true
4141
startretries=3
4242
stdout_logfile=/dev/fd/1

0 commit comments

Comments
 (0)