fix: load API keys from .env at runtime — never embed credentials in cron payloads or source code
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
|
.env
|
||||||
|
|
||||||
# Runtime data — never commit
|
# Runtime data — never commit
|
||||||
data/jobs_queue.json
|
data/jobs_queue.json
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
import { loadEnv } from './lib/env.mjs';
|
||||||
|
loadEnv(); // load .env before anything else
|
||||||
/**
|
/**
|
||||||
* job_applier.mjs — claw-apply Job Applier
|
* job_applier.mjs — claw-apply Job Applier
|
||||||
* Reads jobs queue and applies using the appropriate handler per apply_type
|
* Reads jobs queue and applies using the appropriate handler per apply_type
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
* Searches LinkedIn + Wellfound and populates the jobs queue
|
* Searches LinkedIn + Wellfound and populates the jobs queue
|
||||||
* Run via cron or manually: node job_searcher.mjs
|
* Run via cron or manually: node job_searcher.mjs
|
||||||
*/
|
*/
|
||||||
|
import { loadEnv } from './lib/env.mjs';
|
||||||
|
loadEnv(); // load .env before anything else
|
||||||
|
/**
|
||||||
|
*/
|
||||||
import { dirname, resolve } from 'path';
|
import { dirname, resolve } from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
|||||||
27
lib/env.mjs
Normal file
27
lib/env.mjs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* env.mjs — Load .env file if present
|
||||||
|
* Reads key=value pairs from .env and sets process.env for any missing vars.
|
||||||
|
* Never overwrites vars already set in the environment.
|
||||||
|
*/
|
||||||
|
import { existsSync, readFileSync } from 'fs';
|
||||||
|
import { resolve, dirname } from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const __dir = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const envPath = resolve(__dir, '../.env');
|
||||||
|
|
||||||
|
export function loadEnv() {
|
||||||
|
if (!existsSync(envPath)) return;
|
||||||
|
const lines = readFileSync(envPath, 'utf8').split('\n');
|
||||||
|
for (const line of lines) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||||
|
const eq = trimmed.indexOf('=');
|
||||||
|
if (eq === -1) continue;
|
||||||
|
const key = trimmed.slice(0, eq).trim();
|
||||||
|
const val = trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, '');
|
||||||
|
if (key && !(key in process.env)) {
|
||||||
|
process.env[key] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
setup.mjs
21
setup.mjs
@@ -4,10 +4,12 @@
|
|||||||
* Verifies config, tests logins, registers cron jobs
|
* Verifies config, tests logins, registers cron jobs
|
||||||
* Run once after configuring: node setup.mjs
|
* Run once after configuring: node setup.mjs
|
||||||
*/
|
*/
|
||||||
import { existsSync, mkdirSync } from 'fs';
|
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
||||||
import { dirname, resolve } from 'path';
|
import { dirname, resolve } from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { loadConfig } from './lib/queue.mjs';
|
import { loadConfig } from './lib/queue.mjs';
|
||||||
|
import { loadEnv } from './lib/env.mjs';
|
||||||
|
loadEnv();
|
||||||
|
|
||||||
const __dir = dirname(fileURLToPath(import.meta.url));
|
const __dir = dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
@@ -46,6 +48,16 @@ async function main() {
|
|||||||
mkdirSync(resolve(__dir, 'data'), { recursive: true });
|
mkdirSync(resolve(__dir, 'data'), { recursive: true });
|
||||||
console.log('\n✅ Data directory ready');
|
console.log('\n✅ Data directory ready');
|
||||||
|
|
||||||
|
// Write .env file with API keys (gitignored — never embedded in cron payloads)
|
||||||
|
const envPath = resolve(__dir, '.env');
|
||||||
|
const envLines = [];
|
||||||
|
if (process.env.KERNEL_API_KEY) envLines.push(`KERNEL_API_KEY=${process.env.KERNEL_API_KEY}`);
|
||||||
|
if (process.env.ANTHROPIC_API_KEY) envLines.push(`ANTHROPIC_API_KEY=${process.env.ANTHROPIC_API_KEY}`);
|
||||||
|
if (envLines.length) {
|
||||||
|
writeFileSync(envPath, envLines.join('\n') + '\n', { mode: 0o600 });
|
||||||
|
console.log('✅ API keys written to .env (mode 600, gitignored)');
|
||||||
|
}
|
||||||
|
|
||||||
// Test Telegram
|
// Test Telegram
|
||||||
if (settings.notifications?.bot_token && settings.notifications?.telegram_user_id) {
|
if (settings.notifications?.bot_token && settings.notifications?.telegram_user_id) {
|
||||||
const { sendTelegram } = await import('./lib/notify.mjs');
|
const { sendTelegram } = await import('./lib/notify.mjs');
|
||||||
@@ -88,9 +100,8 @@ async function main() {
|
|||||||
const telegramUserId = settings.notifications?.telegram_user_id;
|
const telegramUserId = settings.notifications?.telegram_user_id;
|
||||||
const searchSchedule = settings.schedules?.search || '0 * * * *';
|
const searchSchedule = settings.schedules?.search || '0 * * * *';
|
||||||
const applySchedule = settings.schedules?.apply || '0 */6 * * *';
|
const applySchedule = settings.schedules?.apply || '0 */6 * * *';
|
||||||
const kernelApiKey = process.env.KERNEL_API_KEY;
|
|
||||||
const anthropicApiKey = process.env.ANTHROPIC_API_KEY || '';
|
|
||||||
|
|
||||||
|
// API keys are loaded from .env at runtime — never embedded in cron payloads
|
||||||
if (gatewayToken && telegramUserId) {
|
if (gatewayToken && telegramUserId) {
|
||||||
console.log('\nRegistering cron jobs...');
|
console.log('\nRegistering cron jobs...');
|
||||||
const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${gatewayToken}` };
|
const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${gatewayToken}` };
|
||||||
@@ -100,7 +111,7 @@ async function main() {
|
|||||||
schedule: { kind: 'cron', expr: searchSchedule, tz: settings.timezone || 'America/Los_Angeles' },
|
schedule: { kind: 'cron', expr: searchSchedule, tz: settings.timezone || 'America/Los_Angeles' },
|
||||||
payload: {
|
payload: {
|
||||||
kind: 'agentTurn',
|
kind: 'agentTurn',
|
||||||
message: `Run the claw-apply job searcher: \`cd ${__dir} && KERNEL_API_KEY=${kernelApiKey}${anthropicApiKey ? ` ANTHROPIC_API_KEY=${anthropicApiKey}` : ''} node job_searcher.mjs 2>&1\`. Report how many new jobs were found.`,
|
message: `Run the claw-apply job searcher: \`cd ${__dir} && node job_searcher.mjs 2>&1\`. Report how many new jobs were found.`,
|
||||||
timeoutSeconds: 0,
|
timeoutSeconds: 0,
|
||||||
},
|
},
|
||||||
delivery: { mode: 'announce', to: telegramUserId },
|
delivery: { mode: 'announce', to: telegramUserId },
|
||||||
@@ -113,7 +124,7 @@ async function main() {
|
|||||||
schedule: { kind: 'cron', expr: applySchedule, tz: settings.timezone || 'America/Los_Angeles' },
|
schedule: { kind: 'cron', expr: applySchedule, tz: settings.timezone || 'America/Los_Angeles' },
|
||||||
payload: {
|
payload: {
|
||||||
kind: 'agentTurn',
|
kind: 'agentTurn',
|
||||||
message: `Run the claw-apply job applier: \`cd ${__dir} && KERNEL_API_KEY=${kernelApiKey}${anthropicApiKey ? ` ANTHROPIC_API_KEY=${anthropicApiKey}` : ''} node job_applier.mjs 2>&1\`. Report results.`,
|
message: `Run the claw-apply job applier: \`cd ${__dir} && node job_applier.mjs 2>&1\`. Report results.`,
|
||||||
timeoutSeconds: 0,
|
timeoutSeconds: 0,
|
||||||
},
|
},
|
||||||
delivery: { mode: 'announce', to: telegramUserId },
|
delivery: { mode: 'announce', to: telegramUserId },
|
||||||
|
|||||||
Reference in New Issue
Block a user