- point MySQL and Whisper settings to the VM - add VM MySQL bootstrap scripts and docs - allow LAN Vite origins for CORS - fix Express 5 validation assignment crash - allow login with username or email - prevent recursive auth refresh retries
130 lines
5.0 KiB
JavaScript
130 lines
5.0 KiB
JavaScript
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import dotenv from "dotenv";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const backendRoot = path.resolve(__dirname, "../..");
|
|
const repoRoot = path.resolve(backendRoot, "..");
|
|
|
|
dotenv.config({ path: path.join(repoRoot, ".env"), quiet: true });
|
|
dotenv.config({ path: path.join(backendRoot, ".env"), override: true, quiet: true });
|
|
|
|
function stringEnv(name, fallback = "") {
|
|
const value = process.env[name];
|
|
return value && value.trim().length > 0 ? value.trim() : fallback;
|
|
}
|
|
|
|
function numberEnv(name, fallback) {
|
|
const value = Number(process.env[name]);
|
|
return Number.isFinite(value) ? value : fallback;
|
|
}
|
|
|
|
function boolEnv(name, fallback = false) {
|
|
const value = process.env[name];
|
|
if (value === undefined) return fallback;
|
|
return ["1", "true", "yes", "on"].includes(value.toLowerCase());
|
|
}
|
|
|
|
function firstString(names, fallback = "") {
|
|
for (const name of names) {
|
|
const value = stringEnv(name);
|
|
if (value) return value;
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
function normalizeBaseUrl(value) {
|
|
return value.replace(/\/+$/, "");
|
|
}
|
|
|
|
const serviceHost = stringEnv("ORPHION_SERVICE_HOST", "127.0.0.1");
|
|
const whisperVmIp = firstString(["WHISPER_VM_IP", "WHISPER_HOST"], serviceHost);
|
|
const whisperVmPort = numberEnv("WHISPER_VM_PORT", numberEnv("WHISPER_PORT", 8000));
|
|
const whisperApiUrl = normalizeBaseUrl(
|
|
firstString(["WHISPER_API_URL"], `http://${whisperVmIp}:${whisperVmPort}`),
|
|
);
|
|
|
|
export const env = {
|
|
appName: "Orphion",
|
|
nodeEnv: stringEnv("NODE_ENV", "development"),
|
|
isProduction: stringEnv("NODE_ENV") === "production",
|
|
host: stringEnv("HOST", "127.0.0.1"),
|
|
port: numberEnv("PORT", 4000),
|
|
backendRoot,
|
|
repoRoot,
|
|
apiPrefix: "/api/v1",
|
|
clientOrigin: stringEnv("CLIENT_ORIGIN", "http://localhost:5173"),
|
|
trustProxy: boolEnv("TRUST_PROXY", false),
|
|
requestBodyLimit: stringEnv("REQUEST_BODY_LIMIT", "2mb"),
|
|
database: {
|
|
host: firstString(["DB_HOST", "MYSQL_HOST"], serviceHost),
|
|
port: numberEnv("DB_PORT", numberEnv("MYSQL_PORT", 3306)),
|
|
name: firstString(["DB_NAME", "MYSQL_DATABASE"], "orphion"),
|
|
user: firstString(["DB_USER", "MYSQL_USER"], "root"),
|
|
password: firstString(["DB_PASSWORD", "MYSQL_PASSWORD"], ""),
|
|
connectionLimit: numberEnv("DB_CONNECTION_LIMIT", numberEnv("MYSQL_CONNECTION_LIMIT", 10)),
|
|
autoMigrate: boolEnv("DB_AUTO_MIGRATE", boolEnv("MYSQL_AUTO_MIGRATE", true)),
|
|
},
|
|
auth: {
|
|
accessTokenSecret: firstString(["JWT_ACCESS_SECRET", "JWT_SECRET"], "replace-access-secret"),
|
|
refreshTokenSecret: firstString(
|
|
["JWT_REFRESH_SECRET"],
|
|
"replace-refresh-secret-with-a-different-long-value",
|
|
),
|
|
accessTokenTtl: stringEnv("JWT_ACCESS_EXPIRES_IN", stringEnv("JWT_EXPIRES_IN", "15m")),
|
|
refreshTokenTtl: stringEnv("JWT_REFRESH_EXPIRES_IN", "30d"),
|
|
accessCookieName: stringEnv("JWT_COOKIE_NAME", "orphion_access_token"),
|
|
refreshCookieName: stringEnv("JWT_REFRESH_COOKIE_NAME", "orphion_refresh_token"),
|
|
cookieSecure: boolEnv("JWT_COOKIE_SECURE", false),
|
|
cookieSameSite: stringEnv("JWT_COOKIE_SAME_SITE", "lax"),
|
|
},
|
|
upload: {
|
|
tempDir: path.resolve(backendRoot, stringEnv("UPLOAD_TEMP_DIR", ".tmp/uploads")),
|
|
maxAudioSizeMb: numberEnv("MAX_AUDIO_SIZE_MB", 200),
|
|
allowedMimeTypes: stringEnv(
|
|
"AUDIO_ALLOWED_MIME_TYPES",
|
|
"audio/webm,audio/wav,audio/mpeg,audio/mp4,audio/ogg,audio/x-m4a,video/webm",
|
|
)
|
|
.split(",")
|
|
.map((item) => item.trim())
|
|
.filter(Boolean),
|
|
},
|
|
storage: {
|
|
driver: stringEnv("STORAGE_DRIVER", "smb"),
|
|
basePath: path.resolve(
|
|
backendRoot,
|
|
stringEnv("STORAGE_BASE_PATH", stringEnv("REMOTE_STORAGE_PATH", "../storage/audio")),
|
|
),
|
|
publicBaseUrl: normalizeBaseUrl(stringEnv("STORAGE_PUBLIC_BASE_URL", "")),
|
|
httpBaseUrl: normalizeBaseUrl(stringEnv("STORAGE_HTTP_BASE_URL", "")),
|
|
httpToken: stringEnv("STORAGE_HTTP_TOKEN", ""),
|
|
s3: {
|
|
endpoint: stringEnv("S3_ENDPOINT", ""),
|
|
region: stringEnv("S3_REGION", "us-east-1"),
|
|
bucket: stringEnv("S3_BUCKET", ""),
|
|
accessKeyId: stringEnv("S3_ACCESS_KEY_ID", ""),
|
|
secretAccessKey: stringEnv("S3_SECRET_ACCESS_KEY", ""),
|
|
forcePathStyle: boolEnv("S3_FORCE_PATH_STYLE", true),
|
|
},
|
|
},
|
|
whisper: {
|
|
vmIp: whisperVmIp,
|
|
vmPort: whisperVmPort,
|
|
apiUrl: whisperApiUrl,
|
|
transcribePath: stringEnv("WHISPER_TRANSCRIBE_PATH", "/transcribe"),
|
|
healthPath: stringEnv("WHISPER_HEALTH_PATH", "/health"),
|
|
fileField: stringEnv("WHISPER_FILE_FIELD", "file"),
|
|
timeoutMs: numberEnv("WHISPER_TIMEOUT_MS", 900000),
|
|
retries: numberEnv("WHISPER_RETRIES", 2),
|
|
retryDelayMs: numberEnv("WHISPER_RETRY_DELAY_MS", 1500),
|
|
modelName: stringEnv("WHISPER_MODEL_NAME", "faster-whisper-large-v3"),
|
|
allowMock: boolEnv("WHISPER_ALLOW_MOCK", false),
|
|
queueConcurrency: numberEnv("TRANSCRIPTION_QUEUE_CONCURRENCY", 1),
|
|
},
|
|
rateLimit: {
|
|
windowMs: numberEnv("RATE_LIMIT_WINDOW_MS", 15 * 60 * 1000),
|
|
max: numberEnv("RATE_LIMIT_MAX", 300),
|
|
authMax: numberEnv("AUTH_RATE_LIMIT_MAX", 30),
|
|
},
|
|
};
|