thepopebot 1.2.75-beta.4 → 1.2.75-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/ai/tools.js CHANGED
@@ -54,6 +54,9 @@ const agentChatCodingTool = tool(
54
54
  const codingAgent = getConfig('CODING_AGENT') || 'claude-code';
55
55
  const containerName = `${codingAgent}-headless-${randomUUID().slice(0, 8)}`;
56
56
 
57
+ const { createAgentJobApiKey } = await import('../db/api-keys.js');
58
+ const { key: agentJobToken } = createAgentJobApiKey(randomUUID());
59
+
57
60
  const { runHeadlessContainer, tailContainerLogs, waitForContainer, removeContainer } = await import('../tools/docker.js');
58
61
  await runHeadlessContainer({
59
62
  containerName,
@@ -64,6 +67,7 @@ const agentChatCodingTool = tool(
64
67
  taskPrompt: prompt,
65
68
  mode,
66
69
  injectSecrets: true,
70
+ agentJobToken,
67
71
  });
68
72
 
69
73
  const streamCallback = runtime.configurable.streamCallback;
@@ -1270,7 +1270,7 @@ export async function initiateOAuthFlow({ secretName, clientId, clientSecret, to
1270
1270
  try {
1271
1271
  const { createOAuthState } = await import('../oauth/helper.js');
1272
1272
  const redirectUri = `${process.env.AUTH_URL}/api/oauth/callback`;
1273
- const state = createOAuthState({ secretName, clientId, clientSecret, tokenUrl, secretType: secretType || 'agent_job_secret', returnPath: returnPath || '/admin/event-handler/agent-jobs' });
1273
+ const state = createOAuthState({ secretName, clientId, clientSecret, tokenUrl, secretType: secretType || 'agent_job_secret', returnPath: returnPath || '/admin/event-handler/agent-secrets' });
1274
1274
  return { state, redirectUri };
1275
1275
  } catch (err) {
1276
1276
  console.error('Failed to initiate OAuth flow:', err);
@@ -1278,6 +1278,21 @@ export async function initiateOAuthFlow({ secretName, clientId, clientSecret, to
1278
1278
  }
1279
1279
  }
1280
1280
 
1281
+ /**
1282
+ * Get stored OAuth credentials for an agent job secret (for re-authorization pre-fill).
1283
+ * @param {string} name
1284
+ */
1285
+ export async function getOAuthSecretCredentials(name) {
1286
+ await requireAuth();
1287
+ try {
1288
+ const { getAgentJobSecretOAuthCredentials } = await import('../db/config.js');
1289
+ return getAgentJobSecretOAuthCredentials(name) || { error: 'Not an OAuth secret' };
1290
+ } catch (err) {
1291
+ console.error('Failed to get OAuth credentials:', err);
1292
+ return { error: 'Failed to get credentials' };
1293
+ }
1294
+ }
1295
+
1281
1296
  /**
1282
1297
  * Delete an agent job secret.
1283
1298
  * @param {string} name
@@ -30,6 +30,7 @@ function ChatHeader({ chatId: chatIdProp, workspaceId }) {
30
30
  setStarred(data.starred || 0);
31
31
  setResolvedChatId(data.chatId);
32
32
  if (data.chatMode) setChatMode(data.chatMode);
33
+ document.title = data.title;
33
34
  }
34
35
  }).catch(() => {
35
36
  });
@@ -41,6 +42,7 @@ function ChatHeader({ chatId: chatIdProp, workspaceId }) {
41
42
  setTitle(data.title);
42
43
  setStarred(data.starred || 0);
43
44
  if (data.chatMode) setChatMode(data.chatMode);
45
+ document.title = data.title;
44
46
  }
45
47
  }).catch(() => {
46
48
  });
@@ -51,6 +53,7 @@ function ChatHeader({ chatId: chatIdProp, workspaceId }) {
51
53
  if (e.detail.chatId === chatId) {
52
54
  setTitle(e.detail.title);
53
55
  if (e.detail.chatMode) setChatMode(e.detail.chatMode);
56
+ document.title = e.detail.title;
54
57
  }
55
58
  };
56
59
  const starHandler = (e) => {
@@ -63,6 +66,7 @@ function ChatHeader({ chatId: chatIdProp, workspaceId }) {
63
66
  return () => {
64
67
  window.removeEventListener("chatTitleUpdated", titleHandler);
65
68
  window.removeEventListener("chatStarUpdated", starHandler);
69
+ document.title = "ThePopeBot";
66
70
  };
67
71
  }, [fetchMeta, chatId]);
68
72
  useEffect(() => {
@@ -38,6 +38,7 @@ export function ChatHeader({ chatId: chatIdProp, workspaceId }) {
38
38
  setStarred(data.starred || 0);
39
39
  setResolvedChatId(data.chatId);
40
40
  if (data.chatMode) setChatMode(data.chatMode);
41
+ document.title = data.title;
41
42
  }
42
43
  })
43
44
  .catch(() => {});
@@ -51,6 +52,7 @@ export function ChatHeader({ chatId: chatIdProp, workspaceId }) {
51
52
  setTitle(data.title);
52
53
  setStarred(data.starred || 0);
53
54
  if (data.chatMode) setChatMode(data.chatMode);
55
+ document.title = data.title;
54
56
  }
55
57
  })
56
58
  .catch(() => {});
@@ -62,6 +64,7 @@ export function ChatHeader({ chatId: chatIdProp, workspaceId }) {
62
64
  if (e.detail.chatId === chatId) {
63
65
  setTitle(e.detail.title);
64
66
  if (e.detail.chatMode) setChatMode(e.detail.chatMode);
67
+ document.title = e.detail.title;
65
68
  }
66
69
  };
67
70
  const starHandler = (e) => {
@@ -74,6 +77,7 @@ export function ChatHeader({ chatId: chatIdProp, workspaceId }) {
74
77
  return () => {
75
78
  window.removeEventListener('chatTitleUpdated', titleHandler);
76
79
  window.removeEventListener('chatStarUpdated', starHandler);
80
+ document.title = 'ThePopeBot';
77
81
  };
78
82
  }, [fetchMeta, chatId]);
79
83
 
@@ -8,7 +8,8 @@ import {
8
8
  getAgentJobSecrets,
9
9
  updateAgentJobSecret,
10
10
  deleteAgentJobSecretAction,
11
- initiateOAuthFlow
11
+ initiateOAuthFlow,
12
+ getOAuthSecretCredentials
12
13
  } from "../actions.js";
13
14
  function buildProviderOptions() {
14
15
  const options = [];
@@ -170,6 +171,16 @@ function AddSecretDialog({ open, onAdd, onCancel, onOAuthSuccess, editingSecret
170
171
  setCopied(false);
171
172
  setRedirectUri(`${window.location.origin}/api/oauth/callback`);
172
173
  if (!editingSecret) setTimeout(() => nameRef.current?.focus(), 50);
174
+ if (editingSecret?.key) {
175
+ getOAuthSecretCredentials(editingSecret.key).then((creds) => {
176
+ if (creds && !creds.error) {
177
+ setClientId(creds.clientId);
178
+ setClientSecret(creds.clientSecret);
179
+ const match = PROVIDER_OPTIONS.find((o) => o.tokenUrl === creds.tokenUrl);
180
+ if (match) setSelectedOption(match.id);
181
+ }
182
+ });
183
+ }
173
184
  }
174
185
  return () => {
175
186
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
@@ -236,7 +247,7 @@ function AddSecretDialog({ open, onAdd, onCancel, onOAuthSuccess, editingSecret
236
247
  tokenUrl: opt.tokenUrl,
237
248
  scopes,
238
249
  secretType: "agent_job_secret",
239
- returnPath: "/admin/event-handler/agent-jobs"
250
+ returnPath: "/admin/event-handler/agent-secrets"
240
251
  });
241
252
  if (result?.error) {
242
253
  setError(result.error);
@@ -9,6 +9,7 @@ import {
9
9
  updateAgentJobSecret,
10
10
  deleteAgentJobSecretAction,
11
11
  initiateOAuthFlow,
12
+ getOAuthSecretCredentials,
12
13
  } from '../actions.js';
13
14
 
14
15
  // ─────────────────────────────────────────────────────────────────────────────
@@ -225,6 +226,19 @@ function AddSecretDialog({ open, onAdd, onCancel, onOAuthSuccess, editingSecret
225
226
  setCopied(false);
226
227
  setRedirectUri(`${window.location.origin}/api/oauth/callback`);
227
228
  if (!editingSecret) setTimeout(() => nameRef.current?.focus(), 50);
229
+
230
+ // Pre-fill OAuth credentials from stored secret
231
+ if (editingSecret?.key) {
232
+ getOAuthSecretCredentials(editingSecret.key).then((creds) => {
233
+ if (creds && !creds.error) {
234
+ setClientId(creds.clientId);
235
+ setClientSecret(creds.clientSecret);
236
+ // Auto-select provider by matching tokenUrl
237
+ const match = PROVIDER_OPTIONS.find((o) => o.tokenUrl === creds.tokenUrl);
238
+ if (match) setSelectedOption(match.id);
239
+ }
240
+ });
241
+ }
228
242
  }
229
243
  return () => {
230
244
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
@@ -306,7 +320,7 @@ function AddSecretDialog({ open, onAdd, onCancel, onOAuthSuccess, editingSecret
306
320
  tokenUrl: opt.tokenUrl,
307
321
  scopes,
308
322
  secretType: 'agent_job_secret',
309
- returnPath: '/admin/event-handler/agent-jobs',
323
+ returnPath: '/admin/event-handler/agent-secrets',
310
324
  });
311
325
 
312
326
  if (result?.error) {
@@ -30,7 +30,7 @@ const EVENT_HANDLER_TABS = [
30
30
  { id: "llms", label: "LLMs", href: "/admin/event-handler/llms" },
31
31
  { id: "chat", label: "Chat", href: "/admin/event-handler/chat" },
32
32
  { id: "coding-agents", label: "Coding Agents", href: "/admin/event-handler/coding-agents" },
33
- { id: "agent-jobs", label: "Agent Jobs", href: "/admin/event-handler/agent-jobs" },
33
+ { id: "agent-secrets", label: "Agent Secrets", href: "/admin/event-handler/agent-secrets" },
34
34
  { id: "webhooks", label: "Webhooks", href: "/admin/event-handler/webhooks" },
35
35
  { id: "telegram", label: "Telegram", href: "/admin/event-handler/telegram" },
36
36
  { id: "voice", label: "Voice", href: "/admin/event-handler/voice" }
@@ -52,7 +52,7 @@ const EVENT_HANDLER_TABS = [
52
52
  { id: 'llms', label: 'LLMs', href: '/admin/event-handler/llms' },
53
53
  { id: 'chat', label: 'Chat', href: '/admin/event-handler/chat' },
54
54
  { id: 'coding-agents', label: 'Coding Agents', href: '/admin/event-handler/coding-agents' },
55
- { id: 'agent-jobs', label: 'Agent Jobs', href: '/admin/event-handler/agent-jobs' },
55
+ { id: 'agent-secrets', label: 'Agent Secrets', href: '/admin/event-handler/agent-secrets' },
56
56
  { id: 'webhooks', label: 'Webhooks', href: '/admin/event-handler/webhooks' },
57
57
  { id: 'telegram', label: 'Telegram', href: '/admin/event-handler/telegram' },
58
58
  { id: 'voice', label: 'Voice', href: '/admin/event-handler/voice' },
@@ -138,6 +138,12 @@ export async function ensureCodeWorkspaceContainer(id) {
138
138
  const chat = getChatByWorkspaceId(id);
139
139
  const injectSecrets = chat?.chatMode === 'agent';
140
140
 
141
+ let agentJobToken;
142
+ if (injectSecrets) {
143
+ const { createAgentJobApiKey } = await import('../db/api-keys.js');
144
+ ({ key: agentJobToken } = createAgentJobApiKey(id));
145
+ }
146
+
141
147
  try {
142
148
  const { inspectContainer, startContainer, removeContainer, runInteractiveContainer } =
143
149
  await import('../tools/docker.js');
@@ -153,6 +159,7 @@ export async function ensureCodeWorkspaceContainer(id) {
153
159
  featureBranch: workspace.featureBranch,
154
160
  workspaceId: id,
155
161
  injectSecrets,
162
+ agentJobToken,
156
163
  });
157
164
  return { status: 'created' };
158
165
  }
@@ -181,6 +188,7 @@ export async function ensureCodeWorkspaceContainer(id) {
181
188
  featureBranch: workspace.featureBranch,
182
189
  workspaceId: id,
183
190
  injectSecrets,
191
+ agentJobToken,
184
192
  });
185
193
  return { status: 'created' };
186
194
  } catch (err) {
@@ -209,6 +217,12 @@ export async function startInteractiveMode(id) {
209
217
  const chat = getChatByWorkspaceId(id);
210
218
  const injectSecrets = chat?.chatMode === 'agent';
211
219
 
220
+ let agentJobToken;
221
+ if (injectSecrets) {
222
+ const { createAgentJobApiKey } = await import('../db/api-keys.js');
223
+ ({ key: agentJobToken } = createAgentJobApiKey(id));
224
+ }
225
+
212
226
  try {
213
227
  const { getConfig } = await import('../config.js');
214
228
  const agent = getConfig('CODING_AGENT') || 'claude-code';
@@ -223,6 +237,7 @@ export async function startInteractiveMode(id) {
223
237
  featureBranch: workspace.featureBranch,
224
238
  workspaceId: id,
225
239
  injectSecrets,
240
+ agentJobToken,
226
241
  });
227
242
 
228
243
  updateContainerName(id, containerName);
@@ -543,13 +558,21 @@ export async function getWorkspaceDiffStats(id, authenticatedUser) {
543
558
  const { execSync } = await import('child_process');
544
559
  const opts = { cwd: repoPath, encoding: 'utf8', timeout: 5000 };
545
560
 
546
- const featureBranch = workspace.featureBranch || workspace.branch || 'main';
547
561
  const baseBranch = workspace.branch || 'main';
548
- let diffRef;
562
+ let currentBranch;
549
563
  try {
550
- execSync(`git -c safe.directory='*' rev-parse --verify origin/${featureBranch} 2>/dev/null`, opts);
551
- diffRef = `origin/${featureBranch}`;
552
- } catch {
564
+ const detected = execSync(`git -c safe.directory='*' rev-parse --abbrev-ref HEAD`, opts).trim();
565
+ currentBranch = (detected && detected !== 'HEAD') ? detected : null;
566
+ } catch { /* leave null */ }
567
+ let diffRef;
568
+ if (currentBranch) {
569
+ try {
570
+ execSync(`git -c safe.directory='*' rev-parse --verify origin/${currentBranch} 2>/dev/null`, opts);
571
+ diffRef = `origin/${currentBranch}`;
572
+ } catch {
573
+ diffRef = `origin/${baseBranch}`;
574
+ }
575
+ } else {
553
576
  diffRef = `origin/${baseBranch}`;
554
577
  }
555
578
 
@@ -618,13 +641,21 @@ export async function getWorkspaceDiffFull(id, authenticatedUser) {
618
641
  const { execSync } = await import('child_process');
619
642
  const opts = { cwd: repoPath, encoding: 'utf8', timeout: 10000 };
620
643
 
621
- const featureBranch = workspace.featureBranch || workspace.branch || 'main';
622
644
  const baseBranch = workspace.branch || 'main';
623
- let diffRef;
645
+ let currentBranch;
624
646
  try {
625
- execSync(`git -c safe.directory='*' rev-parse --verify origin/${featureBranch} 2>/dev/null`, opts);
626
- diffRef = `origin/${featureBranch}`;
627
- } catch {
647
+ const detected = execSync(`git -c safe.directory='*' rev-parse --abbrev-ref HEAD`, opts).trim();
648
+ currentBranch = (detected && detected !== 'HEAD') ? detected : null;
649
+ } catch { /* leave null */ }
650
+ let diffRef;
651
+ if (currentBranch) {
652
+ try {
653
+ execSync(`git -c safe.directory='*' rev-parse --verify origin/${currentBranch} 2>/dev/null`, opts);
654
+ diffRef = `origin/${currentBranch}`;
655
+ } catch {
656
+ diffRef = `origin/${baseBranch}`;
657
+ }
658
+ } else {
628
659
  diffRef = `origin/${baseBranch}`;
629
660
  }
630
661
 
@@ -143,6 +143,9 @@ function writeTraefikConfig() {
143
143
  if (sslDomain) {
144
144
  lines.push(' tls:');
145
145
  lines.push(' certResolver: letsencrypt');
146
+ lines.push(' domains:');
147
+ lines.push(` - main: ${sslDomain}`);
148
+ lines.push(` sans: "*.${sslDomain}"`);
146
149
  }
147
150
  lines.push(` service: ${key}`);
148
151
  }
package/lib/db/config.js CHANGED
@@ -254,6 +254,29 @@ export function setAgentJobSecret(key, value, userId) {
254
254
  .run();
255
255
  }
256
256
 
257
+ /**
258
+ * Get OAuth credentials stored with an agent job secret.
259
+ * Returns { clientId, clientSecret, tokenUrl } if the secret is oauth2, null otherwise.
260
+ * @param {string} key
261
+ */
262
+ export function getAgentJobSecretOAuthCredentials(key) {
263
+ const db = getDb();
264
+ const row = db
265
+ .select({ value: settings.value })
266
+ .from(settings)
267
+ .where(and(eq(settings.type, 'agent_job_secret'), eq(settings.key, key)))
268
+ .get();
269
+ if (!row) return null;
270
+ try {
271
+ const decrypted = decrypt(JSON.parse(row.value));
272
+ const parsed = JSON.parse(decrypted);
273
+ if (parsed.type === 'oauth2' && parsed.clientId && parsed.clientSecret) {
274
+ return { clientId: parsed.clientId, clientSecret: parsed.clientSecret, tokenUrl: parsed.tokenUrl };
275
+ }
276
+ } catch {}
277
+ return null;
278
+ }
279
+
257
280
  /**
258
281
  * Delete an agent job secret.
259
282
  * @param {string} key
@@ -24,12 +24,19 @@ function cleanExpiredAgentJobKeys() {
24
24
  }
25
25
  invalidateApiKeyCache();
26
26
  console.log(`[maintenance] Deleted ${expiredIds.length} expired agent job key(s)`);
27
+ } else {
28
+ console.log(`[maintenance] No expired agent job keys (${rows.length} active)`);
27
29
  }
28
30
  } catch (err) {
29
31
  console.error('[maintenance] cleanExpiredAgentJobKeys failed:', err);
30
32
  }
31
33
  }
32
34
 
35
+ function runMaintenance() {
36
+ console.log('[maintenance] Running maintenance...');
37
+ cleanExpiredAgentJobKeys();
38
+ }
39
+
33
40
  export function startMaintenanceCron() {
34
- cron.schedule('0 * * * *', cleanExpiredAgentJobKeys);
41
+ cron.schedule('0 * * * *', runMaintenance);
35
42
  }
@@ -196,7 +196,7 @@ async function runContainer({ containerName, image, env = [], workingDir, hostCo
196
196
  * @param {boolean} [options.injectSecrets] - Inject agent job secrets into container env
197
197
  * @returns {Promise<{containerId: string, containerName: string}>}
198
198
  */
199
- async function runInteractiveContainer({ containerName, repo, branch, codingAgent, featureBranch, workspaceId, injectSecrets, continueSession = true }) {
199
+ async function runInteractiveContainer({ containerName, repo, branch, codingAgent, featureBranch, workspaceId, injectSecrets, agentJobToken, continueSession = true }) {
200
200
  const agent = codingAgent || getConfig('CODING_AGENT') || 'claude-code';
201
201
  const version = process.env.THEPOPEBOT_VERSION;
202
202
  const image = `stephengpope/thepopebot:coding-agent-${agent}-${version}`;
@@ -234,6 +234,11 @@ async function runInteractiveContainer({ containerName, repo, branch, codingAgen
234
234
  if (jobSecrets.length > 0) {
235
235
  env.push(`AGENT_JOB_SECRETS=${JSON.stringify(Object.fromEntries(jobSecrets.map(s => [s.key, s.value])))}`);
236
236
  }
237
+ if (agentJobToken) {
238
+ env.push(`AGENT_JOB_TOKEN=${agentJobToken}`);
239
+ const appUrl = getConfig('APP_URL');
240
+ if (appUrl) env.push(`APP_URL=${appUrl}`);
241
+ }
237
242
  }
238
243
 
239
244
  const hostConfig = {};
@@ -394,7 +399,7 @@ function buildAgentAuthEnv(agent) {
394
399
  * @param {boolean} [options.injectSecrets] - Inject agent job secrets into container env
395
400
  * @returns {Promise<{containerId: string, containerName: string}>}
396
401
  */
397
- async function runHeadlessContainer({ containerName, repo, branch, featureBranch, workspaceId, taskPrompt, mode = 'plan', codingAgent, systemPrompt, continueSession = true, injectSecrets }) {
402
+ async function runHeadlessContainer({ containerName, repo, branch, featureBranch, workspaceId, taskPrompt, mode = 'plan', codingAgent, systemPrompt, continueSession = true, injectSecrets, agentJobToken }) {
398
403
  const agent = codingAgent || getConfig('CODING_AGENT') || 'claude-code';
399
404
  const version = process.env.THEPOPEBOT_VERSION;
400
405
  const image = `stephengpope/thepopebot:coding-agent-${agent}-${version}`;
@@ -441,6 +446,11 @@ async function runHeadlessContainer({ containerName, repo, branch, featureBranch
441
446
  if (jobSecrets.length > 0) {
442
447
  env.push(`AGENT_JOB_SECRETS=${JSON.stringify(Object.fromEntries(jobSecrets.map(s => [s.key, s.value])))}`);
443
448
  }
449
+ if (agentJobToken) {
450
+ env.push(`AGENT_JOB_TOKEN=${agentJobToken}`);
451
+ const appUrl = getConfig('APP_URL');
452
+ if (appUrl) env.push(`APP_URL=${appUrl}`);
453
+ }
444
454
  }
445
455
 
446
456
  const hostConfig = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thepopebot",
3
- "version": "1.2.75-beta.4",
3
+ "version": "1.2.75-beta.6",
4
4
  "type": "module",
5
5
  "description": "Create autonomous AI agents with a two-layer architecture: Next.js Event Handler + Docker Agent.",
6
6
  "bin": {
@@ -5,9 +5,12 @@
5
5
  *
6
6
  * Prompts for domain, DNS provider, API credentials, and server address.
7
7
  * Creates the wildcard DNS record via the provider's API.
8
- * Writes all config to .env and switches COMPOSE_FILE to docker-compose.custom.yml.
8
+ * Writes SSL config to .env, DNS provider credentials to .env.traefik,
9
+ * and switches COMPOSE_FILE to docker-compose.custom.yml.
9
10
  */
10
11
 
12
+ import fs from 'fs';
13
+ import path from 'path';
11
14
  import * as clack from '@clack/prompts';
12
15
  import { updateEnvVariable } from './lib/auth.mjs';
13
16
  import { loadEnvFile } from './lib/env.mjs';
@@ -299,17 +302,21 @@ async function main() {
299
302
  updateEnvVariable('SSL_DOMAIN', domain);
300
303
  updateEnvVariable('SSL_EMAIL', email);
301
304
  updateEnvVariable('SSL_DNS_PROVIDER', provider.traefikName);
302
- updateEnvVariable('APP_HOSTNAME', domain);
303
305
 
304
- // Write provider-specific credentials (standard names Traefik reads them directly)
305
- for (const envVar of provider.envVars) {
306
- updateEnvVariable(envVar.key, credentials[envVar.key]);
306
+ // Set APP_HOSTNAME only if not already set (don't overwrite existing webhook hostname)
307
+ if (!env.APP_HOSTNAME) {
308
+ updateEnvVariable('APP_HOSTNAME', domain);
307
309
  }
308
310
 
311
+ // Write DNS provider credentials to .env.traefik (Traefik-only, not leaked to other services)
312
+ const traefikEnvPath = path.join(process.cwd(), '.env.traefik');
313
+ const traefikLines = provider.envVars.map((v) => `${v.key}=${credentials[v.key]}`);
314
+ fs.writeFileSync(traefikEnvPath, traefikLines.join('\n') + '\n');
315
+
309
316
  // Switch to custom compose file
310
317
  updateEnvVariable('COMPOSE_FILE', 'docker-compose.custom.yml');
311
318
 
312
- s.stop('Configuration saved to .env');
319
+ s.stop('Configuration saved to .env and .env.traefik');
313
320
 
314
321
  // ── Summary ────────────────────────────────────────────────────────
315
322
  clack.log.success(
@@ -1,6 +1,7 @@
1
1
  # Credentials - NEVER commit these
2
2
  .env
3
3
  .env.local
4
+ .env.traefik
4
5
  *.pem
5
6
  *.key
6
7
 
@@ -8,7 +8,9 @@
8
8
  # SSL_DOMAIN — your domain (e.g., bot.example.com)
9
9
  # SSL_EMAIL — email for Let's Encrypt notifications
10
10
  # SSL_DNS_PROVIDER — Traefik DNS provider name (e.g., cloudflare)
11
- # Provider API creds — standard env var names (e.g., CF_DNS_API_TOKEN for Cloudflare)
11
+ #
12
+ # DNS provider API credentials are stored in .env.traefik (created by setup-ssl).
13
+ # Only this file is passed to the Traefik container — not the main .env.
12
14
  #
13
15
  # DNS: *.${SSL_DOMAIN} must resolve to this server (A record or CNAME).
14
16
  # The setup-ssl command can create this record for you.
@@ -28,7 +30,7 @@ services:
28
30
  - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
29
31
  - --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=${SSL_DNS_PROVIDER}
30
32
  - --certificatesresolvers.letsencrypt.acme.dnschallenge.resolvers=8.8.8.8:53,8.8.4.4:53
31
- env_file: .env
33
+ env_file: .env.traefik
32
34
  ports:
33
35
  - "80:80"
34
36
  - "443:443"
@@ -60,6 +62,8 @@ services:
60
62
  - traefik.http.routers.event-handler.rule=Host(`${APP_HOSTNAME}`)
61
63
  - traefik.http.routers.event-handler.entrypoints=websecure
62
64
  - traefik.http.routers.event-handler.tls.certresolver=letsencrypt
65
+ - traefik.http.routers.event-handler.tls.domains[0].main=${SSL_DOMAIN}
66
+ - traefik.http.routers.event-handler.tls.domains[0].sans=*.${SSL_DOMAIN}
63
67
  - traefik.http.services.event-handler.loadbalancer.server.port=80
64
68
  stop_grace_period: 120s
65
69
  healthcheck: