3 Commits

5 changed files with 87 additions and 14 deletions

View File

@@ -1,7 +1,7 @@
name: cloudsearch
services:
app:
image: gitea.timxx.cn/admin/cloudsearch:v0.4.0
image: gitea.timxx.cn/admin/cloudsearch:latest
container_name: CloudSearch_App
restart: unless-stopped
ports: ["9527:9527"]

View File

@@ -1,3 +1,7 @@
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
export interface Config {
port: number;
nodeEnv: string;
@@ -23,6 +27,50 @@ export interface Config {
dbPath: string;
}
const DEFAULT_JWT_SECRETS = ['', 'cloudsearch-jwt-secret-dev', 'cloudsearch-jwt-secret-2024', 'your-super-secret-jwt-key-change-me'];
const DEFAULT_PASSWORDS = ['', 'admin123', 'admin', 'password', '123456', '0nL5kLhMIJ1121PYmQb25A'];
function loadOrGenerateSecret(key: string, envKey: string, defaultVal: string, isDefault: (v: string) => boolean, byteLen: number): string {
const envVal = process.env[envKey];
// If env var is set and NOT a known default → use it directly
if (envVal && !isDefault(envVal)) {
return envVal;
}
// Try to load from persisted secrets file
const secretsPath = path.join(process.env.DATA_DIR || '/app/data', 'secrets.json');
try {
if (fs.existsSync(secretsPath)) {
const secrets = JSON.parse(fs.readFileSync(secretsPath, 'utf8'));
if (secrets[key]) return secrets[key];
}
} catch (_) {}
// If env var is set but it's a default → still use it (admin set it explicitly, maybe for dev)
if (envVal) return envVal;
// No env var, no persisted file → auto-generate
const generated = byteLen === 16
? crypto.randomBytes(8).toString('hex') // 16-char hex for passwords
: crypto.randomBytes(byteLen).toString('hex'); // 64-char hex for JWT
try {
const dir = path.dirname(secretsPath);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
let existing: Record<string, string> = {};
if (fs.existsSync(secretsPath)) {
try { existing = JSON.parse(fs.readFileSync(secretsPath, 'utf8')); } catch (_) {}
}
existing[key] = generated;
fs.writeFileSync(secretsPath, JSON.stringify(existing, null, 2));
console.log(`[Config] Auto-generated ${envKey} → saved to secrets.json`);
} catch (e: any) {
console.log(`[Config] Could not persist ${envKey}:`, e.message);
}
return generated;
}
const config: Config = {
port: parseInt(process.env.PORT || '9527', 10),
nodeEnv: process.env.NODE_ENV || 'development',
@@ -30,9 +78,9 @@ const config: Config = {
pansouUrl: process.env.PANSOU_URL || 'http://localhost:8888',
pansouAuthToken: process.env.PANSOU_AUTH_TOKEN || '',
videoParserUrl: process.env.VIDEO_PARSER_URL || 'http://localhost:3001',
jwtSecret: process.env.JWT_SECRET || 'cloudsearch-jwt-secret-dev',
jwtSecret: loadOrGenerateSecret('jwt_secret', 'JWT_SECRET', 'cloudsearch-jwt-secret-dev', (v) => DEFAULT_JWT_SECRETS.includes(v), 32),
adminUsername: process.env.ADMIN_USERNAME || 'admin',
adminPassword: process.env.ADMIN_PASSWORD || 'admin123',
adminPassword: loadOrGenerateSecret('admin_password', 'ADMIN_PASSWORD', 'admin123', (v) => DEFAULT_PASSWORDS.includes(v), 16),
validation: {
concurrency: parseInt(process.env.VALIDATION_CONCURRENCY || '10', 10),
timeout: parseInt(process.env.VALIDATION_TIMEOUT || '5000', 10),

View File

@@ -42,14 +42,7 @@ export function validateConfig(): ValidationError[] {
}
// ─── Cookie Encryption ───
if (!process.env.COOKIE_ENCRYPTION_KEY) {
errors.push({
key: 'COOKIE_ENCRYPTION_KEY',
message: '未设置网盘 Cookie 加密密钥Cookie 将以明文存储。生产环境强烈建议设置。\n' +
'生成: openssl rand -hex 32',
severity: 'warn',
});
}
// Key is auto-generated and persisted to encryption.key if COOKIE_ENCRYPTION_KEY is not set
// ─── Port conflict check (best-effort) ───
if (config.port < 1024 && (process as any).getuid?.() !== 0) {

View File

@@ -39,6 +39,7 @@ function runMigrations(db: Database.Database): void {
id INTEGER PRIMARY KEY AUTOINCREMENT,
cloud_type TEXT NOT NULL,
cookie TEXT,
cloud_type_uid TEXT DEFAULT NULL,
nickname TEXT,
is_active INTEGER NOT NULL DEFAULT 1,
storage_used TEXT,
@@ -204,6 +205,7 @@ function migrateCloudConfigs(db: Database.Database): void {
id INTEGER PRIMARY KEY AUTOINCREMENT,
cloud_type TEXT NOT NULL,
cookie TEXT,
cloud_type_uid TEXT DEFAULT NULL,
nickname TEXT,
is_active INTEGER NOT NULL DEFAULT 1,
storage_used TEXT,
@@ -251,6 +253,13 @@ function migrateCloudConfigs(db: Database.Database): void {
db.exec("ALTER TABLE cloud_configs ADD COLUMN notify_config TEXT DEFAULT NULL");
console.log('[DB] cloud_configs migration: notify_config column added');
}
// Migration 6: Add cloud_type_uid column
const hasCloudTypeUid = db.prepare("SELECT sql FROM sqlite_master WHERE name='cloud_configs' AND sql LIKE '%cloud_type_uid%'").get();
if (!hasCloudTypeUid) {
db.exec("ALTER TABLE cloud_configs ADD COLUMN cloud_type_uid TEXT DEFAULT NULL");
console.log('[DB] cloud_configs migration: cloud_type_uid column added');
}
}
}

View File

@@ -6,6 +6,8 @@
* Production MUST set COOKIE_ENCRYPTION_KEY!
*/
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 12; // 96-bit nonce for GCM
@@ -25,9 +27,30 @@ function getKey(): Buffer {
ENCRYPTION_KEY = crypto.createHash('sha256').update(envKey).digest();
console.log('[Crypto] Cookie encryption enabled (key from COOKIE_ENCRYPTION_KEY, SHA-256 derived)');
} else {
// Default stable key (not ephemeral) — data survives container restart
ENCRYPTION_KEY = crypto.createHash('sha256').update('cloudsearch-cookie-key-v1').digest();
console.log('[Crypto] Cookie encryption enabled (built-in default key — set COOKIE_ENCRYPTION_KEY in .env for extra security)');
// Auto-generate a random key and persist to file
const keyFile = path.join(process.env.DATA_DIR || '/app/data', 'encryption.key');
try {
if (fs.existsSync(keyFile)) {
const savedKey = fs.readFileSync(keyFile, 'utf8').trim();
if (savedKey.length >= 32) {
ENCRYPTION_KEY = crypto.createHash('sha256').update(savedKey).digest();
console.log('[Crypto] Cookie encryption enabled (loaded from encryption.key)');
return ENCRYPTION_KEY;
}
}
} catch (_) { /* file read failed, will generate new key */ }
const autoKey = crypto.randomBytes(32).toString('hex');
try {
const dir = path.dirname(keyFile);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(keyFile, autoKey);
console.log('[Crypto] Auto-generated encryption key saved to encryption.key');
} catch (e: any) {
console.log('[Crypto] Could not persist key, using ephemeral:', e.message);
}
ENCRYPTION_KEY = crypto.createHash('sha256').update(autoKey).digest();
console.log('[Crypto] Cookie encryption enabled (auto-generated key)');
}
return ENCRYPTION_KEY;
}