NepsenX commited on
Commit
e10fb47
·
verified ·
1 Parent(s): 89b6172

Update src/index.ts

Browse files
Files changed (1) hide show
  1. src/index.ts +188 -254
src/index.ts CHANGED
@@ -4,327 +4,261 @@ import * as fs from 'node:fs';
4
  import * as path from 'node:path';
5
  import { fileURLToPath } from 'node:url';
6
  import axios from 'axios';
7
- import nodemailer from 'nodemailer';
8
 
9
- // ============================================================
10
- // কনফিগারেশন
11
- // ============================================================
12
  const __filename = fileURLToPath(import.meta.url);
13
  const __dirname = path.dirname(__filename);
14
 
15
  interface Config {
16
- RES1_REPO: string;
17
- GITHUB_PAT: string;
18
- VERSIONS_DIR: string;
19
- HEALTH_PORT: number;
20
- PROD_PORT: number;
21
- TEST_PORT: number;
22
- ADMIN_EMAIL: string;
23
- SMTP_USER: string;
24
- SMTP_PASS: string;
25
  }
26
 
27
  const CONFIG: Config = {
28
- RES1_REPO: 'https://github.com/NepsenX/Oracus-AI.git',
29
- GITHUB_PAT: process.env.GITHUB_PAT || '',
30
- VERSIONS_DIR: '/app/versions',
31
- HEALTH_PORT: 7860,
32
- PROD_PORT: 7000,
33
- TEST_PORT: 2000,
34
- ADMIN_EMAIL: 'oracusai.nepsenx@gmail.com',
35
- SMTP_USER: 'oracusai.nepsenx@gmail.com',
36
- SMTP_PASS: process.env.SMTP_PASS || 'your-app-password'
37
  };
38
 
39
- // PAT না থাকলে Exit
40
  if (!CONFIG.GITHUB_PAT) {
41
- console.error('❌ GITHUB_PAT environment variable is not set!');
42
- console.error('Please add GITHUB_PAT to Repository Secrets.');
43
- process.exit(1);
44
  }
45
 
46
- // ============================================================
47
- // স্টেট ম্যানেজমেন্ট
48
- // ============================================================
49
- let prodProcess: ChildProcess | null = null;
50
- let prodVersionPath: string | null = null;
51
- let isUpdating: boolean = false;
52
 
53
  // ============================================================
54
- // ইমেইল ফাংশন
55
  // ============================================================
56
  async function sendEmail(subject: string, text: string): Promise<void> {
57
- try {
58
- const transporter = nodemailer.createTransport({
59
- service: 'gmail',
60
- auth: { user: CONFIG.SMTP_USER, pass: CONFIG.SMTP_PASS }
61
- });
62
- await transporter.sendMail({
63
- from: CONFIG.SMTP_USER,
64
- to: CONFIG.ADMIN_EMAIL,
65
- subject: `[Oracus Runner] ${subject}`,
66
- text: text
67
- });
68
- console.log(`📧 Email sent: ${subject}`);
69
- } catch (err: any) {
70
- console.error('❌ Failed to send email:', err.message);
 
71
  }
 
 
 
72
  }
73
 
74
  // ============================================================
75
- // গিট ইউটিলিটি (PAT সহ)
76
  // ============================================================
77
  function runGitCommand(cmd: string, cwd: string): Promise<string> {
78
- return new Promise((resolve, reject) => {
79
- exec(cmd, { cwd }, (error, stdout, stderr) => {
80
- if (error) reject(stderr || error.message);
81
- else resolve(stdout.trim());
82
- });
83
  });
 
84
  }
85
 
86
  function getRepoUrlWithPat(repoUrl: string): string {
87
- if (repoUrl.includes('@')) return repoUrl;
88
- return repoUrl.replace('https://', `https://${CONFIG.GITHUB_PAT}@`);
89
  }
90
 
91
  async function getCurrentCommitSha(repoUrl: string): Promise<string | null> {
92
- try {
93
- const urlWithPat = getRepoUrlWithPat(repoUrl);
94
- const output = await runGitCommand(`git ls-remote ${urlWithPat} main`, process.cwd());
95
- return output.split(/\s/)[0];
96
- } catch (err: any) {
97
- console.error('⚠️ Failed to fetch SHA:', err.message);
98
- return null;
99
- }
100
  }
101
 
102
  async function cloneOrPull(repoUrl: string, targetDir: string): Promise<void> {
103
- const urlWithPat = getRepoUrlWithPat(repoUrl);
104
- if (!fs.existsSync(targetDir)) {
105
- console.log(`📥 Cloning ${repoUrl} into ${targetDir}...`);
106
- await runGitCommand(`git clone ${urlWithPat} ${targetDir}`, process.cwd());
107
- } else {
108
- console.log(`🔄 Pulling latest in ${targetDir}...`);
109
- await runGitCommand(`git pull`, targetDir);
110
- }
111
  }
112
 
113
- // ============================================================
114
- // 📂 মেমোরি কপি ইউটিলিটি (শুধু লোকাল)
115
- // ============================================================
116
- function copyMemoryFolder(srcVersionPath: string, dstVersionPath: string): void {
117
- const srcMemory = path.join(srcVersionPath, 'memory');
118
- const dstMemory = path.join(dstVersionPath, 'memory');
119
-
120
- if (fs.existsSync(srcMemory)) {
121
- // গন্তব্য memory ফোল্ডার মুছে ফেলো
122
- if (fs.existsSync(dstMemory)) {
123
- fs.rmSync(dstMemory, { recursive: true, force: true });
124
- }
125
- // সোর্স থেকে কপি করো
126
- fs.cpSync(srcMemory, dstMemory, { recursive: true });
127
- console.log(`📂 Memory copied from ${srcMemory} to ${dstMemory}`);
128
- } else {
129
- console.log(`⚠️ Source memory folder not found: ${srcMemory}`);
130
- }
131
  }
132
 
133
  // ============================================================
134
- // 🚀 এজেন্ট ডিপ্লয় (পোর্ট প্যারামিটার সহ)
135
  // ============================================================
136
  function deployAgent(versionDir: string, port: number): Promise<ChildProcess> {
137
- return new Promise((resolve, reject) => {
138
- console.log(`🚀 Starting agent on port ${port} from ${versionDir}...`);
139
-
140
- // ১. ডিপেন্ডেন্সি বিল্ড
141
- exec(`npm install && npm run build`, { cwd: versionDir }, (err) => {
142
- if (err) {
143
- return reject(`Build failed: ${err.message}`);
144
- }
145
-
146
- // ২. প্রসেস স্পন (পোর্ট এনভ ভেরিয়েবল হিসেবে)
147
- const proc = spawn('npm', ['start'], {
148
- cwd: versionDir,
149
- env: { ...process.env, PORT: String(port) },
150
- stdio: 'pipe'
151
- });
152
-
153
- proc.stdout.on('data', (data) => console.log(`[AGENT:${port}] ${data}`));
154
- proc.stderr.on('data', (data) => console.error(`[AGENT ERR:${port}] ${data}`));
155
-
156
- // ৩. হেলথ চেক (৮ সেকেন্ড অপেক্ষা)
157
- setTimeout(async () => {
158
- try {
159
- const res = await axios.get(`http://localhost:${port}/health`, { timeout: 3000 });
160
- if (res.status === 200) {
161
- console.log(`✅ Health check passed on port ${port}!`);
162
- resolve(proc);
163
- } else {
164
- reject(`Health check failed (non-200) on port ${port}`);
165
- }
166
- } catch (err: any) {
167
- reject(`Health check error on port ${port}: ${err.message}`);
168
- }
169
- }, 8000);
170
- });
171
  });
 
172
  }
173
 
174
  // ============================================================
175
- // 🧠 মূল আপডেট লজিক (আপনার বর্ণিত ফ্লো - Res3 বাদ)
176
- // ============================================================
 
 
177
  async function checkAndUpdate(): Promise<void> {
178
- if (isUpdating) {
179
- console.log('⏳ Update already in progress...');
180
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  }
182
 
183
- isUpdating = true;
184
- let testProcess: ChildProcess | null = null;
185
- let testVersionPath: string | null = null;
186
-
187
- try {
188
- console.log('🔍 Checking for updates...');
189
-
190
- // ১. Res1-এর সর্বশেষ SHA বের করা
191
- const latestSha = await getCurrentCommitSha(CONFIG.RES1_REPO);
192
- if (!latestSha) throw new Error('Could not fetch latest SHA');
193
-
194
- // ২. বর্তমান প্রোডাকশন SHA বের করা
195
- let currentSha: string | null = null;
196
- if (prodVersionPath && fs.existsSync(path.join(prodVersionPath, '.git'))) {
197
- try {
198
- currentSha = await runGitCommand('git rev-parse HEAD', prodVersionPath);
199
- } catch {}
200
- }
201
-
202
- // ৩. যদি SHA একই হয়, তাহলে কিছু করো না
203
- if (latestSha === currentSha) {
204
- console.log('✅ Already up to date.');
205
- isUpdating = false;
206
- return;
207
- }
208
-
209
- console.log(`🆕 New version detected: ${latestSha}. Deploying V2 on port ${CONFIG.TEST_PORT}...`);
210
-
211
- // ৪. নতুন ভার্সন ক্লোন করো (V2)
212
- testVersionPath = path.join(CONFIG.VERSIONS_DIR, `v2_${Date.now()}`);
213
- await cloneOrPull(CONFIG.RES1_REPO, testVersionPath);
214
-
215
- // ৫. V2-কে পোর্ট ২০০০-এ চালাও (টেস্ট)
216
- testProcess = await deployAgent(testVersionPath, CONFIG.TEST_PORT);
217
- console.log(`✅ V2 passed health check on port ${CONFIG.TEST_PORT}!`);
218
-
219
- // ৬. সফল! এখন প্রমোশন শুরু করো
220
-
221
- // ৬.১: ৭০০০-এর memory ফোল্ডার ২০০০-এ কপি করো (যদি ৭০০০-এর Agent আগে থেকে চালু থাকে)
222
- if (prodVersionPath) {
223
- copyMemoryFolder(prodVersionPath, testVersionPath);
224
- } else {
225
- console.log('⚠️ No previous version found. Starting fresh without memory copy.');
226
- }
227
-
228
- // ৬.২: ২০০০-এর Agent বন্ধ করো (memory লক হয়ে যেতে পারে)
229
- if (testProcess) {
230
- console.log('🛑 Stopping test agent on port 2000...');
231
- testProcess.kill('SIGTERM');
232
- await new Promise(resolve => setTimeout(resolve, 2000));
233
- if (!testProcess.killed) {
234
- testProcess.kill('SIGKILL');
235
- }
236
- }
237
 
238
- // ৬.৩: এখন ২০০০-এর কোড দিয়ে ৭০০০-এ চালাও (memory সহ)
239
- console.log(`🚀 Promoting V2 to production port ${CONFIG.PROD_PORT}...`);
240
- const newProdProc = await deployAgent(testVersionPath, CONFIG.PROD_PORT);
 
 
241
 
242
- // ৬.৪: পুরনো ৭০০০ বন্ধ করো
243
- if (prodProcess) {
244
- console.log('🛑 Stopping old production agent on port 7000...');
245
- prodProcess.kill('SIGTERM');
246
- setTimeout(() => {
247
- if (!prodProcess?.killed) prodProcess?.kill('SIGKILL');
248
- }, 3000);
249
- }
250
 
251
- // ৬.৫: স্টেট আপডেট করো
252
- prodProcess = newProdProc;
253
- prodVersionPath = testVersionPath;
254
- testProcess = null;
255
- testVersionPath = null;
256
 
257
- console.log(` Successfully deployed V2 (${latestSha}) on port ${CONFIG.PROD_PORT}`);
258
- await sendEmail('Deployment Successful', `Version ${latestSha} is now LIVE on port 7000.`);
259
 
260
- } catch (error: any) {
261
- console.error(' Deployment failed:', error);
 
 
 
262
 
263
- // ৭. ❌ ব্যর্থ! ২০০০ বন্ধ করো, ৭০০০ অপরিবর্তিত রাখো
264
- if (testProcess) {
265
- console.log('🛑 Stopping failed test agent on port 2000...');
266
- testProcess.kill('SIGTERM');
267
- setTimeout(() => {
268
- if (!testProcess?.killed) testProcess?.kill('SIGKILL');
269
- }, 2000);
270
- }
271
 
272
- // ব্যর্থতার ইমেইল পাঠাও
273
- await sendEmail('Deployment FAILED', `Error: ${error.message}. Port 7000 is unchanged.`);
274
 
275
- if (prodProcess) {
276
- console.log('🔄 Keeping old version running on port 7000.');
277
- } else {
278
- console.log('⚠️ No previous version available. Port 7000 is empty.');
279
- }
280
- } finally {
281
- isUpdating = false;
282
  }
 
 
 
 
 
 
283
  }
284
 
285
  // ============================================================
286
- // HTTP সার্ভার (পোর্ট ৭৮৬০ - UptimeRobot পাবে)
287
  // ============================================================
288
  const server = http.createServer(async (req, res) => {
289
- const url = new URL(req.url || '/', `http://${req.headers.host}`);
290
-
291
- if (url.pathname === '/') {
292
- res.writeHead(200, { 'Content-Type': 'text/plain' });
293
- res.end('Oracus Runner is Alive! (Port 7860)');
294
- setImmediate(() => checkAndUpdate());
295
- } else if (url.pathname === '/force-update') {
296
- res.writeHead(200);
297
- res.end('Force update triggered.');
298
- setImmediate(() => checkAndUpdate());
299
- } else if (url.pathname === '/status') {
300
- const status = {
301
- prodRunning: prodProcess !== null,
302
- prodVersion: prodVersionPath ? path.basename(prodVersionPath) : 'none',
303
- isUpdating: isUpdating
304
- };
305
- res.writeHead(200, { 'Content-Type': 'application/json' });
306
- res.end(JSON.stringify(status, null, 2));
307
- } else {
308
- res.writeHead(404);
309
- res.end('Not Found');
310
- }
311
  });
312
 
313
  server.listen(CONFIG.HEALTH_PORT, '0.0.0.0', () => {
314
- console.log(`✅ Health server running on port ${CONFIG.HEALTH_PORT}`);
315
- console.log('⏳ Initial setup: Deploying V1 on port 7000...');
316
- checkAndUpdate();
317
  });
318
 
319
- // ============================================================
320
- // প্রসেস হ্যান্ডলিং
321
- // ============================================================
322
  process.on('uncaughtException', (err) => {
323
- console.error('💥 Uncaught Exception:', err);
324
- sendEmail('CRASH - Uncaught Exception', err.stack || err.message);
325
  });
326
-
327
  process.on('unhandledRejection', (reason) => {
328
- console.error('💥 Unhandled Rejection:', reason);
329
- sendEmail('CRASH - Unhandled Rejection', String(reason));
330
  });
 
4
  import * as path from 'node:path';
5
  import { fileURLToPath } from 'node:url';
6
  import axios from 'axios';
7
+ import { Resend } from 'resend';
8
 
 
 
 
9
  const __filename = fileURLToPath(import.meta.url);
10
  const __dirname = path.dirname(__filename);
11
 
12
  interface Config {
13
+ RES1_REPO: string;
14
+ GITHUB_PAT: string;
15
+ VERSIONS_DIR: string;
16
+ HEALTH_PORT: number;
17
+ PROD_PORT: number;
18
+ TEST_PORT: number;
19
+ ADMIN_EMAIL: string;
20
+ RESEND_API_KEY: string;
21
+ FROM_EMAIL: string;
22
  }
23
 
24
  const CONFIG: Config = {
25
+ RES1_REPO: 'https://github.com/NepsenX/Oracus-AI.git',
26
+ GITHUB_PAT: process.env.GITHUB_PAT || '',
27
+ VERSIONS_DIR: '/app/versions',
28
+ HEALTH_PORT: 7860,
29
+ PROD_PORT: 7000,
30
+ TEST_PORT: 2000,
31
+ ADMIN_EMAIL: 'oracusai.nepsenx@gmail.com',
32
+ RESEND_API_KEY: process.env.RESEND_API_KEY || '',
33
+ FROM_EMAIL: 'onboarding@resend.dev' // Resend-এর ডিফল্ট sender
34
  };
35
 
 
36
  if (!CONFIG.GITHUB_PAT) {
37
+ console.error('❌ GITHUB_PAT not set!');
38
+ process.exit(1);
 
39
  }
40
 
41
+ if (!CONFIG.RESEND_API_KEY) {
42
+ console.warn('⚠️ RESEND_API_KEY not set. Email notifications disabled.');
43
+ }
44
+
45
+ const resend = CONFIG.RESEND_API_KEY ? new Resend(CONFIG.RESEND_API_KEY) : null;
 
46
 
47
  // ============================================================
48
+ // ইমেইল ফাংশন (Resend API - SMTP লাগে না!)
49
  // ============================================================
50
  async function sendEmail(subject: string, text: string): Promise<void> {
51
+ if (!resend) {
52
+ console.log(`📧 [SKIP] Email would be sent: "${subject}"`);
53
+ return;
54
+ }
55
+ try {
56
+ const { data, error } = await resend.emails.send({
57
+ from: CONFIG.FROM_EMAIL,
58
+ to: [CONFIG.ADMIN_EMAIL],
59
+ subject: `[Oracus Runner] ${subject}`,
60
+ text: text
61
+ });
62
+ if (error) {
63
+ console.error('❌ Resend error:', error);
64
+ } else {
65
+ console.log(`✅ Email sent! ID: ${data?.id}`);
66
  }
67
+ } catch (err: any) {
68
+ console.error(`⚠️ Email failed (ignored): ${err.message}`);
69
+ }
70
  }
71
 
72
  // ============================================================
73
+ // গিট ইউটিলিটি
74
  // ============================================================
75
  function runGitCommand(cmd: string, cwd: string): Promise<string> {
76
+ return new Promise((resolve, reject) => {
77
+ exec(cmd, { cwd }, (error, stdout, stderr) => {
78
+ if (error) reject(stderr || error.message);
79
+ else resolve(stdout.trim());
 
80
  });
81
+ });
82
  }
83
 
84
  function getRepoUrlWithPat(repoUrl: string): string {
85
+ if (repoUrl.includes('@')) return repoUrl;
86
+ return repoUrl.replace('https://', `https://${CONFIG.GITHUB_PAT}@`);
87
  }
88
 
89
  async function getCurrentCommitSha(repoUrl: string): Promise<string | null> {
90
+ try {
91
+ const urlWithPat = getRepoUrlWithPat(repoUrl);
92
+ const output = await runGitCommand(`git ls-remote ${urlWithPat} main`, process.cwd());
93
+ return output.split(/\s/)[0];
94
+ } catch {
95
+ return null;
96
+ }
 
97
  }
98
 
99
  async function cloneOrPull(repoUrl: string, targetDir: string): Promise<void> {
100
+ const urlWithPat = getRepoUrlWithPat(repoUrl);
101
+ if (!fs.existsSync(targetDir)) {
102
+ console.log(`📥 Cloning into ${targetDir}...`);
103
+ await runGitCommand(`git clone ${urlWithPat} ${targetDir}`, process.cwd());
104
+ } else {
105
+ console.log(`🔄 Pulling latest...`);
106
+ await runGitCommand(`git pull`, targetDir);
107
+ }
108
  }
109
 
110
+ function copyMemoryFolder(srcPath: string, dstPath: string): void {
111
+ const srcMemory = path.join(srcPath, 'memory');
112
+ const dstMemory = path.join(dstPath, 'memory');
113
+ if (fs.existsSync(srcMemory)) {
114
+ if (fs.existsSync(dstMemory)) fs.rmSync(dstMemory, { recursive: true, force: true });
115
+ fs.cpSync(srcMemory, dstMemory, { recursive: true });
116
+ console.log(`📂 Memory copied`);
117
+ }
 
 
 
 
 
 
 
 
 
 
118
  }
119
 
120
  // ============================================================
121
+ // এজেন্ট ডিপ্লয়
122
  // ============================================================
123
  function deployAgent(versionDir: string, port: number): Promise<ChildProcess> {
124
+ return new Promise((resolve, reject) => {
125
+ console.log(`🚀 Starting agent on port ${port}...`);
126
+ exec(`npm install && npm run build`, { cwd: versionDir }, (err) => {
127
+ if (err) return reject(`Build failed: ${err.message}`);
128
+ const proc = spawn('npm', ['start'], {
129
+ cwd: versionDir,
130
+ env: { ...process.env, PORT: String(port) },
131
+ stdio: 'pipe'
132
+ });
133
+ proc.stdout.on('data', (data) => console.log(`[AGENT:${port}] ${data}`));
134
+ proc.stderr.on('data', (data) => console.error(`[AGENT ERR:${port}] ${data}`));
135
+ setTimeout(async () => {
136
+ try {
137
+ const res = await axios.get(`http://localhost:${port}/health`, { timeout: 3000 });
138
+ if (res.status === 200) {
139
+ console.log(`✅ Health check passed on port ${port}!`);
140
+ resolve(proc);
141
+ } else reject(`Health check failed on port ${port}`);
142
+ } catch (err: any) {
143
+ reject(`Health check error on port ${port}: ${err.message}`);
144
+ }
145
+ }, 8000);
 
 
 
 
 
 
 
 
 
 
 
 
146
  });
147
+ });
148
  }
149
 
150
  // ============================================================
151
+ let prodProcess: ChildProcess | null = null;
152
+ let prodVersionPath: string | null = null;
153
+ let isUpdating: boolean = false;
154
+
155
  async function checkAndUpdate(): Promise<void> {
156
+ if (isUpdating) {
157
+ console.log('⏳ Update already in progress...');
158
+ return;
159
+ }
160
+ isUpdating = true;
161
+ let testProcess: ChildProcess | null = null;
162
+ let testVersionPath: string | null = null;
163
+
164
+ try {
165
+ console.log('🔍 Checking for updates...');
166
+ const latestSha = await getCurrentCommitSha(CONFIG.RES1_REPO);
167
+ if (!latestSha) throw new Error('Could not fetch latest SHA');
168
+
169
+ let currentSha: string | null = null;
170
+ if (prodVersionPath && fs.existsSync(path.join(prodVersionPath, '.git'))) {
171
+ try { currentSha = await runGitCommand('git rev-parse HEAD', prodVersionPath); } catch {}
172
  }
173
 
174
+ if (latestSha === currentSha) {
175
+ console.log('✅ Already up to date.');
176
+ isUpdating = false;
177
+ return;
178
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
+ console.log(`🆕 New version: ${latestSha}. Deploying on port ${CONFIG.TEST_PORT}...`);
181
+ testVersionPath = path.join(CONFIG.VERSIONS_DIR, `v2_${Date.now()}`);
182
+ await cloneOrPull(CONFIG.RES1_REPO, testVersionPath);
183
+ testProcess = await deployAgent(testVersionPath, CONFIG.TEST_PORT);
184
+ console.log(`✅ V2 passed health check!`);
185
 
186
+ if (prodVersionPath) copyMemoryFolder(prodVersionPath, testVersionPath);
 
 
 
 
 
 
 
187
 
188
+ if (testProcess) {
189
+ testProcess.kill('SIGTERM');
190
+ await new Promise(resolve => setTimeout(resolve, 2000));
191
+ if (!testProcess.killed) testProcess.kill('SIGKILL');
192
+ }
193
 
194
+ console.log(`🚀 Promoting to production port ${CONFIG.PROD_PORT}...`);
195
+ const newProdProc = await deployAgent(testVersionPath, CONFIG.PROD_PORT);
196
 
197
+ if (prodProcess) {
198
+ console.log('🛑 Stopping old production...');
199
+ prodProcess.kill('SIGTERM');
200
+ setTimeout(() => { if (!prodProcess?.killed) prodProcess?.kill('SIGKILL'); }, 3000);
201
+ }
202
 
203
+ prodProcess = newProdProc;
204
+ prodVersionPath = testVersionPath;
205
+ testProcess = null;
206
+ testVersionPath = null;
 
 
 
 
207
 
208
+ console.log(`✅ Successfully deployed V2 (${latestSha}) on port ${CONFIG.PROD_PORT}`);
209
+ await sendEmail('Deployment Successful', `Version ${latestSha} is now LIVE on port 7000.`);
210
 
211
+ } catch (error: any) {
212
+ console.error(' Deployment failed:', error);
213
+ if (testProcess) {
214
+ testProcess.kill('SIGTERM');
215
+ setTimeout(() => { if (!testProcess?.killed) testProcess?.kill('SIGKILL'); }, 2000);
 
 
216
  }
217
+ await sendEmail('Deployment FAILED', `Error: ${error.message}. Port 7000 is unchanged.`);
218
+ if (prodProcess) console.log('🔄 Keeping old version running.');
219
+ else console.log('⚠️ No previous version available.');
220
+ } finally {
221
+ isUpdating = false;
222
+ }
223
  }
224
 
225
  // ============================================================
226
+ // HTTP সার্ভার
227
  // ============================================================
228
  const server = http.createServer(async (req, res) => {
229
+ const url = new URL(req.url || '/', `http://${req.headers.host}`);
230
+ if (url.pathname === '/') {
231
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
232
+ res.end('Oracus Runner is Alive!');
233
+ setImmediate(() => checkAndUpdate());
234
+ } else if (url.pathname === '/force-update') {
235
+ res.writeHead(200);
236
+ res.end('Force update triggered.');
237
+ setImmediate(() => checkAndUpdate());
238
+ } else if (url.pathname === '/status') {
239
+ res.writeHead(200, { 'Content-Type': 'application/json' });
240
+ res.end(JSON.stringify({
241
+ prodRunning: prodProcess !== null,
242
+ prodVersion: prodVersionPath ? path.basename(prodVersionPath) : 'none',
243
+ isUpdating
244
+ }, null, 2));
245
+ } else {
246
+ res.writeHead(404);
247
+ res.end('Not Found');
248
+ }
 
 
249
  });
250
 
251
  server.listen(CONFIG.HEALTH_PORT, '0.0.0.0', () => {
252
+ console.log(`✅ Health server running on port ${CONFIG.HEALTH_PORT}`);
253
+ console.log('⏳ Initial setup...');
254
+ checkAndUpdate();
255
  });
256
 
 
 
 
257
  process.on('uncaughtException', (err) => {
258
+ console.error('💥 Uncaught Exception:', err);
259
+ sendEmail('CRASH - Uncaught Exception', err.stack || err.message);
260
  });
 
261
  process.on('unhandledRejection', (reason) => {
262
+ console.error('💥 Unhandled Rejection:', reason);
263
+ sendEmail('CRASH - Unhandled Rejection', String(reason));
264
  });