Generate a session secret instead of refusing to start
Build and Push Docker Image / build-and-push (push) Failing after 20s

setup.sh starts the production server without SESSION_SECRET, so the new
"refuse to start" check stopped the app after an update. When the variable
is unset in production the server now makes a random secret once and keeps
it in DATA_DIR/.session-secret (mode 600, git-ignored).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-06 08:18:02 -04:00
1 parent f274bc9c8a
commit 31fb3f958d
3 files changed
+24 -7

No files matched your search

+21 -6
View File
@@ -2,11 +2,12 @@ import express from 'express';
import compression from 'compression';
import session from 'express-session';
import QRCode from 'qrcode';
import { randomUUID } from 'crypto';
import { randomUUID, randomBytes } from 'crypto';
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import {
initDb, getAllProjects, getProject, getProjectEdited, upsertProject, deleteProject,
initDb, dataDir, getAllProjects, getProject, getProjectEdited, upsertProject, deleteProject,
countUsers, createUser, getUserByEmail, getUserById, claimOrphanProjects,
enableTotp, disableTotp, setBackupCodeHashes, setPodcastName,
getShareToken, setShareToken, clearShareToken, getProjectByShareToken,
@@ -50,9 +51,23 @@ const app = express();
const PORT = process.env.PORT || 3001;
const isProd = process.env.NODE_ENV === 'production';
if (isProd && !process.env.SESSION_SECRET) {
console.error('SESSION_SECRET is not set. Set it to a long random string before running in production.');
process.exit(1);
// Session cookies are signed with SESSION_SECRET. When it isn't set (setup.sh doesn't set one), a random secret
// is made once and kept in DATA_DIR, so it survives restarts and nobody has to configure anything.
function sessionSecret() {
if (process.env.SESSION_SECRET) return process.env.SESSION_SECRET;
if (!isProd) return 'dev-only-secret-change-me';
const file = join(dataDir(), '.session-secret');
try {
if (existsSync(file)) return readFileSync(file, 'utf8').trim();
mkdirSync(dataDir(), { recursive: true });
const made = randomBytes(48).toString('hex');
writeFileSync(file, made, { mode: 0o600 });
console.log(`SESSION_SECRET is not set; generated one and saved it to ${file}.`);
return made;
} catch (err) {
console.error('Could not read or save a session secret; set SESSION_SECRET instead.', err.message);
process.exit(1);
}
}
// Trust the reverse proxy (needed for secure cookies to work behind nginx/etc).
@@ -98,7 +113,7 @@ app.use(compression());
app.use(express.json({ limit: '10mb' }));
app.use(session({
store: new SqliteSessionStore(),
secret: process.env.SESSION_SECRET || 'dev-only-secret-change-me',
secret: sessionSecret(),
resave: false,
saveUninitialized: false,
rolling: true,