Skip to content

Commit b4be96c

Browse files
committed
feat: support separate db vars + file version
1 parent 69dfad2 commit b4be96c

File tree

5 files changed

+112
-11
lines changed

5 files changed

+112
-11
lines changed

src/lib/config/read/env.ts

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { log } from '@/lib/logger';
2-
import { parse } from './transform';
32
import { readFileSync } from 'node:fs';
3+
import { parse } from './transform';
44

55
export type EnvType = 'string' | 'string[]' | 'number' | 'boolean' | 'byte' | 'ms' | 'json';
6-
export function env(property: string, env: string | string[], type: EnvType, isDb: boolean = false) {
6+
export function env(property: string, env: string, type: EnvType, isDb: boolean = false) {
77
return {
88
variable: env,
99
property,
@@ -16,7 +16,14 @@ export const ENVS = [
1616
env('core.port', 'CORE_PORT', 'number'),
1717
env('core.hostname', 'CORE_HOSTNAME', 'string'),
1818
env('core.secret', 'CORE_SECRET', 'string'),
19-
env('core.databaseUrl', ['DATABASE_URL', 'CORE_DATABASE_URL'], 'string'),
19+
20+
env('core.databaseUrl', 'DATABASE_URL', 'string'),
21+
// or
22+
env('core.database.username', 'DATABASE_USERNAME', 'string', true),
23+
env('core.database.password', 'DATABASE_PASSWORD', 'string', true),
24+
env('core.database.host', 'DATABASE_HOST', 'string', true),
25+
env('core.database.port', 'DATABASE_PORT', 'number', true),
26+
env('core.database.name', 'DATABASE_NAME', 'string', true),
2027

2128
env('datasource.type', 'DATASOURCE_TYPE', 'string'),
2229
env('datasource.s3.accessKeyId', 'DATASOURCE_S3_ACCESS_KEY_ID', 'string'),
@@ -161,11 +168,62 @@ export const PROP_TO_ENV: Record<string, string | string[]> = Object.fromEntries
161168
ENVS.map((env) => [env.property, env.variable]),
162169
);
163170

171+
export const REQUIRED_DB_VARS = [
172+
'DATABASE_USERNAME',
173+
'DATABASE_PASSWORD',
174+
'DATABASE_HOST',
175+
'DATABASE_PORT',
176+
'DATABASE_NAME',
177+
];
178+
164179
type EnvResult = {
165180
env: Record<string, any>;
166181
dbEnv: Record<string, any>;
167182
};
168183

184+
export function checkDbVars(): boolean {
185+
if (process.env.DATABASE_URL) return true;
186+
187+
for (let i = 0; i !== REQUIRED_DB_VARS.length; ++i) {
188+
if (process.env[REQUIRED_DB_VARS[i]] === undefined) {
189+
return false;
190+
}
191+
}
192+
193+
return true;
194+
}
195+
196+
export function readDbVars(): Record<string, string> {
197+
const logger = log('config').c('readDbVars');
198+
199+
if (process.env.DATABASE_URL) return { DATABASE_URL: process.env.DATABASE_URL };
200+
201+
const dbVars: Record<string, string> = {};
202+
for (let i = 0; i !== REQUIRED_DB_VARS.length; ++i) {
203+
const value = process.env[REQUIRED_DB_VARS[i]];
204+
const valueFileName = process.env[`${REQUIRED_DB_VARS[i]}_FILE`];
205+
if (valueFileName) {
206+
try {
207+
dbVars[REQUIRED_DB_VARS[i]] = readFileSync(valueFileName, 'utf-8').trim();
208+
} catch {
209+
logger.error(`Failed to read database env value from file for ${REQUIRED_DB_VARS[i]}. Exiting...`);
210+
process.exit(1);
211+
}
212+
} else if (value) {
213+
dbVars[REQUIRED_DB_VARS[i]] = value;
214+
}
215+
}
216+
217+
if (!Object.keys(dbVars).length || Object.keys(dbVars).length !== REQUIRED_DB_VARS.length) {
218+
logger.error(
219+
`No database environment variables found (DATABASE_URL or all of [${REQUIRED_DB_VARS.join(', ')}]), exiting...`,
220+
);
221+
process.exit(1);
222+
}
223+
224+
return dbVars;
225+
}
226+
169227
export function readEnv(): EnvResult {
170228
const logger = log('config').c('readEnv');
171229
const envResult: EnvResult = {
@@ -175,9 +233,6 @@ export function readEnv(): EnvResult {
175233

176234
for (let i = 0; i !== ENVS.length; ++i) {
177235
const env = ENVS[i];
178-
if (Array.isArray(env.variable)) {
179-
env.variable = env.variable.find((v) => process.env[v] !== undefined) || 'DATABASE_URL';
180-
}
181236

182237
let value = process.env[env.variable];
183238
const valueFileName = process.env[`${env.variable}_FILE`];

src/lib/config/read/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ export const rawConfig: any = {
1414
returnHttpsUrls: undefined,
1515
tempDirectory: undefined,
1616
trustProxy: undefined,
17+
database: {
18+
username: undefined,
19+
password: undefined,
20+
host: undefined,
21+
port: undefined,
22+
name: undefined,
23+
},
1724
},
1825
chunks: {
1926
max: undefined,

src/lib/config/validate.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,36 @@ export const schema = z.object({
6767
});
6868
}
6969
}),
70-
databaseUrl: z.url(),
7170
returnHttpsUrls: z.boolean().default(false),
7271
defaultDomain: z.string().nullable().default(null),
7372
tempDirectory: z
7473
.string()
7574
.transform((s) => resolve(s))
7675
.default(join(tmpdir(), 'zipline')),
7776
trustProxy: z.boolean().default(false),
77+
78+
databaseUrl: z.url(),
79+
80+
database: z
81+
.object({
82+
username: z.string().nullable().default(null),
83+
password: z.string().nullable().default(null),
84+
host: z.string().nullable().default(null),
85+
port: z.number().nullable().default(null),
86+
name: z.string().nullable().default(null),
87+
})
88+
.superRefine((val, c) => {
89+
const values = Object.values(val);
90+
const someSet = values.some((v) => v !== null);
91+
const allSet = values.every((v) => v !== null);
92+
93+
if (someSet && !allSet) {
94+
c.addIssue({
95+
code: 'custom',
96+
message: 'If one database field is set, all fields must be set',
97+
});
98+
}
99+
}),
78100
}),
79101
chunks: z.object({
80102
max: z.string().default('95mb'),

src/lib/db/index.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { type Prisma, PrismaClient } from '@/prisma/client';
44
import { metadataSchema } from './models/incompleteFile';
55
import { metricDataSchema } from './models/metric';
66
import { userViewSchema } from './models/user';
7+
import { readDbVars, REQUIRED_DB_VARS } from '../config/read/env';
78

89
const building = !!process.env.ZIPLINE_BUILD;
910

@@ -31,12 +32,27 @@ function parseDbLog(env: string): Prisma.LogLevel[] {
3132
.filter((v) => v) as unknown as Prisma.LogLevel[];
3233
}
3334

35+
function pgConnectionString() {
36+
const vars = readDbVars();
37+
if (vars.DATABASE_URL) return vars.DATABASE_URL;
38+
39+
return `postgresql://${vars.DATABASE_USERNAME}:${vars.DATABASE_PASSWORD}@${vars.DATABASE_HOST}:${vars.DATABASE_PORT}/${vars.DATABASE_NAME}`;
40+
}
41+
3442
function getClient() {
3543
const logger = log('db');
3644

37-
logger.info('connecting to database ' + process.env.DATABASE_URL);
45+
const connectionString = pgConnectionString();
46+
if (!connectionString) {
47+
logger.error(`either DATABASE_URL or all of [${REQUIRED_DB_VARS.join(', ')}] not set, exiting...`);
48+
process.exit(1);
49+
}
50+
51+
process.env.DATABASE_URL = connectionString;
52+
53+
logger.info('connecting to database', { url: connectionString });
3854

39-
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
55+
const adapter = new PrismaPg({ connectionString });
4056
const client = new PrismaClient({
4157
adapter,
4258
log: process.env.ZIPLINE_DB_LOG ? parseDbLog(process.env.ZIPLINE_DB_LOG) : undefined,

src/server/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { bytes } from '@/lib/bytes';
22
import { reloadSettings } from '@/lib/config';
3+
import { checkDbVars, REQUIRED_DB_VARS } from '@/lib/config/read/env';
34
import { getDatasource } from '@/lib/datasource';
45
import { prisma } from '@/lib/db';
56
import { runMigrations } from '@/lib/db/migration';
@@ -46,8 +47,8 @@ async function main() {
4647
const argv = process.argv.slice(2);
4748
logger.info('starting zipline', { mode: MODE, version: version, argv });
4849

49-
if (!process.env.DATABASE_URL) {
50-
logger.error('DATABASE_URL not set, exiting...');
50+
if (!checkDbVars()) {
51+
logger.error(`either DATABASE_URL or all of [${REQUIRED_DB_VARS.join(', ')}] not set, exiting...`);
5152
process.exit(1);
5253
}
5354

0 commit comments

Comments
 (0)