Load worker secrets from Vault
Some checks failed
Build and deploy Worker / build (push) Failing after 23s

This commit is contained in:
Ruslan Bakiev
2026-05-08 16:57:41 +07:00
parent a6203bde71
commit b231eb6a27
4 changed files with 70 additions and 2 deletions

View File

@@ -5,7 +5,6 @@ RUN npm ci
FROM node:22-alpine AS build FROM node:22-alpine AS build
WORKDIR /app WORKDIR /app
ENV DATABASE_URL="postgresql://mapflow:mapflow@localhost:5432/mapflow"
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/node_modules ./node_modules
COPY . . COPY . .
RUN npm run prisma:generate RUN npm run prisma:generate

View File

@@ -1,5 +1,9 @@
import 'dotenv/config'; import 'dotenv/config';
import { loadVaultEnvironment } from './vault/env.js';
await loadVaultEnvironment();
export const config = { export const config = {
databaseUrl: process.env.DATABASE_URL ?? '', databaseUrl: process.env.DATABASE_URL ?? '',
workerName: process.env.HATCHET_WORKER_NAME ?? 'mapflow-hatchet-worker', workerName: process.env.HATCHET_WORKER_NAME ?? 'mapflow-hatchet-worker',

View File

@@ -1,7 +1,6 @@
import 'dotenv/config'; import 'dotenv/config';
import { config } from '../config.js'; import { config } from '../config.js';
import { hatchet } from './hatchet-client.js';
import { processVoiceExperienceWorkflow } from './workflows/process-voice-experience.js'; import { processVoiceExperienceWorkflow } from './workflows/process-voice-experience.js';
function resolveWorkerSlots(): number { function resolveWorkerSlots(): number {
@@ -14,6 +13,7 @@ async function main() {
throw new Error('HATCHET_CLIENT_TOKEN is required for hatchet worker'); throw new Error('HATCHET_CLIENT_TOKEN is required for hatchet worker');
} }
const { hatchet } = await import('./hatchet-client.js');
const worker = await hatchet.worker(config.workerName, { const worker = await hatchet.worker(config.workerName, {
workflows: [processVoiceExperienceWorkflow], workflows: [processVoiceExperienceWorkflow],
slots: resolveWorkerSlots(), slots: resolveWorkerSlots(),

65
src/vault/env.ts Normal file
View File

@@ -0,0 +1,65 @@
type VaultConfig = {
address: string;
token: string;
mount: string;
sharedPath: string;
projectPath: string;
};
function requireEnv(name: string) {
const value = process.env[name];
if (!value) {
throw new Error(`${name} is required when VAULT_ENABLED=true.`);
}
return value;
}
function vaultConfig(): VaultConfig {
return {
address: requireEnv('VAULT_ADDR').replace(/\/$/, ''),
token: requireEnv('VAULT_TOKEN'),
mount: requireEnv('VAULT_KV_MOUNT'),
sharedPath: requireEnv('VAULT_SHARED_PATH'),
projectPath: requireEnv('VAULT_PROJECT_PATH'),
};
}
async function readVaultPath(config: VaultConfig, path: string) {
const response = await fetch(
`${config.address}/v1/${config.mount}/data/${path}`,
{ headers: { 'X-Vault-Token': config.token } },
);
if (!response.ok) {
throw new Error(`Vault read failed for ${path}: ${response.status}.`);
}
const payload = (await response.json()) as {
data?: { data?: Record<string, unknown> };
};
const data = payload.data?.data;
if (!data) {
throw new Error(`Vault path ${path} has no KV v2 data.`);
}
return data;
}
function applyEnvironment(values: Record<string, unknown>) {
for (const [key, value] of Object.entries(values)) {
if (typeof value !== 'string') {
throw new Error(`Vault value ${key} must be a string.`);
}
process.env[key] = value;
}
}
export async function loadVaultEnvironment() {
if (process.env.VAULT_ENABLED !== 'true') {
return;
}
const config = vaultConfig();
applyEnvironment(await readVaultPath(config, config.sharedPath));
applyEnvironment(await readVaultPath(config, config.projectPath));
}