circleci
deploy 8b5028b
307187e
Raw
History Blame Contribute Delete
15.2 kB
import './instrument.js';
import 'dotenv/config';
import dns from 'node:dns';
import { startCipherRotation } from './utils/network/cipher.util.js';
startCipherRotation();
import express, { Request, Response, NextFunction } from 'express';
import compression from 'compression';
import helmet from 'helmet';
import { rateLimit } from 'express-rate-limit';
import * as Sentry from '@sentry/node'; // skipcq: JS-C1003
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { traceContext } from './utils/infra/trace.util.js';
import { randomUUID } from 'node:crypto';
import db from './utils/infra/db.util.js';
import videoRoutes from './routes/video.routes.js';
import keyChangerRoutes from './routes/keychanger.routes.js';
import remixRoutes from './routes/remix.routes.js';
import {
requireApiKey,
requireLocalOrApiKey,
assertProdConfig,
} from './utils/network/auth.util.js';
import { logger } from './utils/infra/logger.util.js';
import { setupGracefulShutdown } from './utils/infra/shutdown.util.js';
import { configureServerTimeouts } from './utils/infra/server-timeouts.util.js';
import { closeAllRedis } from './utils/infra/redis.util.js';
import { startJanitor } from './utils/infra/janitor.util.js';
import { warmUp } from './utils/infra/warmup.util.js';
import {
metricsMiddleware,
getMetrics,
recordFailure,
} from './utils/infra/metrics.util.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const STEMS_BASE_DIR = path.join(__dirname, '../temp/remix_stems');
// termux bypass
if (process.platform === 'android') {
try {
const { Module } = await import('module');
const originalRequire = Module.prototype.require;
Module.prototype.require = function (
this: unknown,
name: string,
...args: unknown[]
) {
if (name === '@ffmpeg-installer/ffmpeg') {
return {
path: 'ffmpeg',
version: 'system',
url: 'https://ffmpeg.org/',
};
}
if (name.includes('libsql')) {
try {
return (
originalRequire as (...innerArgs: unknown[]) => unknown
).apply(this, [name, ...args]);
} catch (_ERROR) {
console.debug('[System] LibSQL bypass error:', _ERROR);
console.warn(
`[System] LibSQL native library '${name}' not found, bypassing...`
);
return {
createClient: () => ({
execute: () => Promise.resolve({ rows: [] }),
batch: () => Promise.resolve([]),
close: () => {},
}),
};
}
}
if (name === 'msgpackr-extract' || name === 'cpu-features') {
return null;
}
return (originalRequire as (...innerArgs: unknown[]) => unknown).apply(
this,
[name, ...args]
);
};
console.log('[System] Mocked native modules for Termux compatibility');
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
console.warn(
`[System] Failed to mock @ffmpeg-installer/ffmpeg: ${message}`
);
}
}
const dnsModule = dns as unknown as {
setDefaultResultOrder?: (order: 'ipv4first' | 'ipv6first') => void;
};
if (dnsModule.setDefaultResultOrder) {
dnsModule.setDefaultResultOrder('ipv4first');
}
// global errors
process.on('unhandledRejection', (reason: unknown) => {
const message = reason instanceof Error ? reason.message : String(reason);
console.error(`[Unhandled] reason: ${message}`);
});
process.on('uncaughtException', (err: unknown) => {
const message = err instanceof Error ? err.message : String(err);
console.error(`[Uncaught] error: ${message}`);
if (err instanceof Error && err.stack) console.error(err.stack);
});
const app = express();
app.set('trust proxy', 1);
// observe latency + outcomes
app.use(metricsMiddleware);
const PORT = Number(process.env.PORT) || 5000;
app.use(
helmet({
crossOriginResourcePolicy: { policy: 'cross-origin' },
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
// no inline scripts; external bundles only
scriptSrc: ["'self'"],
// styles still inline (react/libs); hardened later
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'", 'https:', 'wss:'],
},
},
})
);
const globalLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true,
legacyHeaders: false,
message: { error: 'Too many requests, please try again later.' },
});
app.use('/api/', globalLimiter);
const infoLimiter = rateLimit({
windowMs: 1 * 60 * 1000,
max: 15,
standardHeaders: true,
legacyHeaders: false,
message: { error: 'Extraction rate limit exceeded. Slow down!' },
});
app.use(['/info', '/stream-urls'], infoLimiter);
// skip HEAD to allow resumes
const convertLimiter = rateLimit({
windowMs: 5 * 60 * 1000,
max: 60,
standardHeaders: true,
legacyHeaders: false,
skip: (req: Request) => req.method === 'HEAD',
message: { error: 'Download rate limit exceeded. Slow down!' },
});
app.use('/convert', convertLimiter);
// seeder is expensive to run
const seedLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 10,
standardHeaders: true,
legacyHeaders: false,
message: { error: 'Seeder rate limit exceeded.' },
});
app.use('/seed-intelligence', seedLimiter);
// disable SSE compression
app.use(
compression({
filter: (req, res) => {
if (req.path === '/events') return false;
if (res.getHeader('Content-Type') === 'text/event-stream') return false;
return compression.filter(req, res);
},
})
);
app.use((req: Request, res: Response, next: NextFunction) => {
const traceId =
(req.headers['x-correlation-id'] as string) ||
(req.query.id as string) ||
randomUUID().split('-')[0];
res.setHeader('X-Trace-Id', traceId);
Sentry.withIsolationScope((scope) => {
if (process.env.SENTRY_DSN) {
scope.setTag('traceId', traceId);
}
traceContext.run({ traceId }, () => {
next();
});
});
});
app.use((req: Request, res: Response, next: NextFunction) => {
if (req.path === '/ping' || req.method === 'OPTIONS') {
next();
return;
}
const timestamp = new Date().toLocaleTimeString('en-US', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
const traceId =
(traceContext.getStore() as { traceId?: string })?.traceId || 'global';
console.log(
`[${timestamp}] [${traceId}] ${req.method} ${req.originalUrl || req.url}`
);
next();
});
// cors middleware
app.use((req: Request, res: Response, next: NextFunction) => {
const origin = req.headers.origin as string | undefined;
const allowlist = (process.env.ALLOWED_ORIGINS || '')
.split(',')
.map((entry) => entry.trim())
.filter(Boolean);
if (origin) {
const openMode = allowlist.length === 0;
if (openMode || allowlist.includes(origin)) {
res.header('Access-Control-Allow-Origin', origin);
res.header('Vary', 'Origin');
// credentials unsafe with reflected origin
if (!openMode) res.header('Access-Control-Allow-Credentials', 'true');
}
}
res.header(
'Access-Control-Allow-Methods',
'GET, POST, PUT, DELETE, OPTIONS, PATCH'
);
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control, Last-Event-ID, ngrok-skip-browser-warning, bypass-tunnel-reminder, sentry-trace, baggage'
);
// eme worker reads these for progress
res.header(
'Access-Control-Expose-Headers',
'Content-Length, Content-Range, Accept-Ranges'
);
if (req.method === 'OPTIONS') {
res.status(200).end();
return;
}
next();
});
console.log('--- Environment Check ---');
console.log(`PORT: ${PORT}`);
console.log(
`COOKIES_URL: ${process.env.COOKIES_URL ? '✅ LOADED' : '❌ MISSING'}`
);
console.log(
`GEMINI_API_KEY: ${process.env.GEMINI_API_KEY ? '✅ LOADED' : '❌ MISSING'}`
);
console.log(
`GROQ_API_KEY: ${process.env.GROQ_API_KEY ? '✅ LOADED' : '❌ MISSING'}`
);
console.log(
`BILIBILI_COOKIE: ${process.env.BILIBILI_COOKIE ? '✅ LOADED' : '➖ not set (720p cap)'}`
);
console.log(
`INFO CACHE: ${process.env.DISABLE_INFO_CACHE && process.env.DISABLE_INFO_CACHE !== '0' ? '🚫 DISABLED (testing)' : '✅ ENABLED'}`
);
dns.lookup('google.com', { family: 4 }, (err, addr) => {
const status = err ? '❌ FAILED' : `✅ ${addr}`;
console.log(`DNS google.com: ${status}`);
});
dns.lookup('youtube.com', { family: 4 }, (err, addr) => {
const status = err ? '❌ FAILED' : `✅ ${addr}`;
console.log(`DNS youtube.com: ${status}`);
});
console.log('-------------------------');
app.use((_req: Request, res: Response, next: NextFunction) => {
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
res.setHeader('Cross-Origin-Embedder-Policy', 'credentialless');
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
next();
});
app.use(express.json({ limit: '1mb' }));
app.use(express.urlencoded({ limit: '1mb', extended: true }));
// core routes
const TEMP_DIR = path.join(__dirname, 'temp');
const CACHE_DIR = path.join(TEMP_DIR, 'yt-dlp-cache');
[TEMP_DIR, CACHE_DIR].forEach((dir) => {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
});
console.log('[System] Initializing routes...');
app.get('/ping', (_req: Request, res: Response) => {
res.status(200).send('pong');
});
app.get('/api/get-url', async (_req: Request, res: Response) => {
try {
if (db) {
const result = (await db.execute({
sql: "SELECT value FROM configs WHERE key = 'BACKEND_URL' LIMIT 1",
args: [],
})) as unknown as { rows: Array<{ value: string }> };
if (result.rows.length > 0) {
res.json({ url: result.rows[0].value });
return;
}
}
} catch (error) {
console.error('[Discovery] Error fetching URL:', error);
}
res.json({ url: null });
});
// opt-in auth; off unless API_KEY set
app.use(
[
'/info',
'/stream-urls',
'/convert',
'/proxy',
'/api/remix',
'/api/key-changer',
],
requireApiKey
);
app.use('/', videoRoutes);
app.use('/api/key-changer', keyChangerRoutes);
app.use('/api/remix', remixRoutes);
console.log('[System] Routes ready');
if (process.env.SENTRY_DSN) {
Sentry.setupExpressErrorHandler(app);
}
app.get('/health', (_req: Request, res: Response) => {
res.status(200).json({
status: 'ok',
port: PORT,
});
});
// gated: localhost or API_KEY only
app.get('/metrics', requireLocalOrApiKey, (_req: Request, res: Response) => {
res.status(200).json(getMetrics());
});
// global error handler
app.use((err: unknown, req: Request, res: Response, _next: NextFunction) => {
const reason = err instanceof Error ? err.name : 'UnknownError';
recordFailure(reason);
logger.error({ err, path: req.path, method: req.method }, 'request error');
if (!res.headersSent) {
// hide internals from clients in production
const details =
process.env.NODE_ENV === 'production'
? undefined
: err instanceof Error
? err.message
: typeof err === 'string'
? err
: JSON.stringify(err);
res.status(500).json({ error: 'Internal Server Error', details });
}
});
const distPath = path.join(__dirname, '../../frontend/dist');
if (fs.existsSync(distPath) && process.env.API_ONLY !== 'true') {
app.use(express.static(distPath));
app.get(/.*/u, (req: Request, res: Response, next: NextFunction) => {
if (
req.path.startsWith('/events') ||
req.path.startsWith('/info') ||
req.path.startsWith('/convert') ||
req.path.startsWith('/stream-urls') ||
req.path.startsWith('/proxy') ||
req.path.startsWith('/api') ||
req.path.includes('/EME_STREAM_DOWNLOAD/')
) {
next();
return;
}
res.sendFile(path.join(distPath, 'index.html'));
});
} else {
app.get('/', (_req: Request, res: Response) => {
res.send('YouTube to MP4 Backend is running!');
});
}
export default app;
if (process.env.NODE_ENV !== 'test') {
assertProdConfig();
const server = app.listen(PORT, '0.0.0.0', () => {
console.log(`Server is running on port ${PORT}`);
configureServerTimeouts(server);
setupGracefulShutdown(server, { onClose: closeAllRedis });
import('./services/ytdlp/info.js').then(({ startPeerKeepWarm }) =>
startPeerKeepWarm()
);
import('./services/ytdlp/config.js').then(({ bootstrapCookies }) =>
bootstrapCookies()
);
import('node:child_process').then(({ exec, spawn: spawnChild }) => {
exec('yt-dlp --version', (err, stdout) => {
if (err) console.error(`yt-dlp check failed: ${err.message}`);
else console.log(`yt-dlp: ${stdout.trim()}`);
});
exec('ffmpeg -version', (err, stdout) => {
if (err) console.error(`FFmpeg check failed: ${err.message}`);
else console.log(`FFmpeg: ${stdout.split('\n')[0]}`);
});
// pot opt-in; bgutil currently fails botguard
const potEnabled = process.env.ENABLE_POT_PLUGIN === '1';
if (!potEnabled) {
console.log('[PO Token] disabled; set ENABLE_POT_PLUGIN=1 to enable');
} else {
const potCandidates = [
process.env.HOME
? path.resolve(
process.env.HOME,
'bgutil-ytdlp-pot-provider/server/build/main.js'
)
: null,
'/root/bgutil-ytdlp-pot-provider/server/build/main.js',
'/data/data/com.termux/files/home/bgutil-ytdlp-pot-provider/server/build/main.js',
].filter((candidate): candidate is string => Boolean(candidate));
try {
const potScript = potCandidates.find((candidate) =>
fs.existsSync(candidate)
);
if (potScript) {
const pot = spawnChild('node', [potScript], {
stdio: 'ignore',
detached: true,
});
pot.unref();
console.log(
`PO Token server started (pid: ${pot.pid}, port: 4416)`
);
} else {
console.log(
`[PO Token] Server script not found in: ${potCandidates.join(', ')}`
);
}
} catch (error: unknown) {
console.error('[PO Token] Spawn failed:', (error as Error).message);
}
}
});
warmUp();
// log memory levers for resource tuning
setTimeout(() => {
const mem = process.memoryUsage();
const mb = (bytes: number) => Math.round(bytes / 1048576);
const limit = process.env.KOYEB_INSTANCE_MEMORY_MB || '?';
console.log(
`[Mem] idle snapshot: rss=${mb(mem.rss)}MB heapUsed=${mb(mem.heapUsed)}MB ` +
`heapTotal=${mb(mem.heapTotal)}MB external=${mb(mem.external)}MB ` +
`arrayBuffers=${mb(mem.arrayBuffers)}MB (instance limit=${limit}MB)`
);
}, 6000).unref();
});
}
startJanitor({ tempDir: TEMP_DIR, stemsBaseDir: STEMS_BASE_DIR, db });