WalleGriffkinder commited on
Commit
55b839b
·
verified ·
1 Parent(s): 5cccb35

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +80 -150
server.js CHANGED
@@ -2,24 +2,25 @@ import express from 'express';
2
  import fs from 'fs/promises';
3
  import fssync from 'fs';
4
  import path from 'path';
5
- import { spawn } from 'child_process';
6
  import fetch from 'node-fetch';
7
  import { glob } from 'glob';
 
8
 
9
  // --- Конфигурация ---
10
- const EXPRESS_PORT = parseInt(process.env.EXPRESS_PORT || '3001', 10);
 
11
  const TELEGRAM_DATA_DIR = process.env.TELEGRAM_DATA_DIR || '/var/lib/telegram-bot-api';
12
  const FILES_TTL_HOURS = parseInt(process.env.FILES_TTL || '-1', 10);
 
 
13
 
14
  const GITHUB_USERNAME = process.env.GITHUB_USERNAME || '';
15
  const GITHUB_TOKEN = process.env.GITHUB_TOKEN || '';
16
  const ENV_GIST_ID = process.env.ENV_GIST_ID || '';
17
 
18
- let currentTunnelUrl = ''; // Будет обновляться при запуске туннеля
19
-
20
  const app = express();
21
 
22
- // ... стальные вспомогательные функции без изменений) ...
23
  function formatBytes(bytes, decimals = 2) {
24
  if (bytes === 0) return '0 Bytes';
25
  const k = 1024;
@@ -37,9 +38,7 @@ async function getDirectoryStats(dirPath) {
37
  try {
38
  const stats = await fs.stat(file);
39
  totalSize += stats.size;
40
- } catch (e) {
41
- // Игнорируем ошибки для отдельных файлов (например, если файл удален во время сканирования)
42
- }
43
  }
44
  return {
45
  fileCount: files.length,
@@ -59,211 +58,142 @@ async function cleanupOldFiles(dirPath, ttlHours) {
59
  console.log(`[TTL] Starting cleanup for files older than ${ttlHours} hours in ${dirPath}`);
60
  const now = Date.now();
61
  const ttlMs = ttlHours * 60 * 60 * 1000;
62
- let processed = 0;
63
- let deleted = 0;
64
- let errors = 0;
65
-
66
  try {
67
  const files = await glob(`${dirPath}/**/*`, { nodir: true, dot: true, stat: true, withFileTypes: false });
68
-
69
  for (const file of files) {
70
  processed++;
71
  try {
72
- const stats = await fs.stat(file);
73
  if (stats.isFile()) {
74
- const fileAge = now - stats.mtimeMs;
75
- if (fileAge > ttlMs) {
76
  await fs.unlink(file);
77
  deleted++;
78
- if (deleted % 100 === 0) console.log(`[TTL] Deleted ${deleted} old files so far...`);
79
  }
80
  }
81
- } catch (e) {
82
- console.error(`[TTL] Error processing file ${file}:`, e.message);
83
- errors++;
84
- }
85
  }
86
  } catch (globError) {
87
  console.error(`[TTL] Error during glob search:`, globError);
88
  return { processed, deleted, errors: errors + 1, message: `Glob error: ${globError.message}` };
89
  }
90
-
91
  const result = { processed, deleted, errors, message: `Cleanup completed. Processed: ${processed}, Deleted: ${deleted}, Errors: ${errors}` };
92
  console.log(`[TTL] ${result.message}`);
93
  return result;
94
  }
95
 
96
-
97
  // --- Обновление Gist ---
98
- async function updateEnvGistInGithub(tunnelUrlToSave) {
99
  if (!GITHUB_USERNAME || !GITHUB_TOKEN || !ENV_GIST_ID) {
100
  console.warn('Gist update skipped: GITHUB_USERNAME, GITHUB_TOKEN, or ENV_GIST_ID is not set.');
101
  return;
102
  }
103
  try {
104
  const spaceId = process.env.SPACE_ID || 'N/A';
105
- const spaceHost = process.env.SPACE_HOST || 'N/A';
106
-
107
- // Используем более простое и статичное имя файла для Gist
108
- const gistFilename = `hf_space_info.json`;
109
 
 
110
  const content = {
111
  last_updated: new Date().toISOString(),
112
  space_id: spaceId,
113
- space_host: spaceHost,
114
- tools_tunnel_url: tunnelUrlToSave || 'pending...',
115
- telegram_api_main_port: 7860,
116
- tools_app_internal_port: EXPRESS_PORT,
 
 
117
  files_ttl_hours: FILES_TTL_HOURS > 0 ? FILES_TTL_HOURS : 'disabled',
118
  };
119
-
120
  const gistData = {
121
- description: `Hugging Face Space Info - ${spaceId}`, // Описание можно оставить динамическим
122
- files: {
123
- [gistFilename]: { // Используем новое имя файла
124
- content: JSON.stringify(content, null, 2),
125
- },
126
- },
127
  };
128
-
129
- console.log(`Attempting to update Gist ${ENV_GIST_ID} with filename ${gistFilename}`);
130
  const response = await fetch(`https://api.github.com/gists/${ENV_GIST_ID}`, {
131
  method: 'PATCH',
132
  headers: {
133
- 'Authorization': `token ${GITHUB_TOKEN}`,
134
- 'Accept': 'application/vnd.github.v3+json',
135
- 'User-Agent': 'HFSpaceTgAPITools',
136
- 'Content-Type': 'application/json',
137
  },
138
  body: JSON.stringify(gistData),
139
  });
140
-
141
- if (!response.ok) {
142
- const errorBody = await response.text();
143
- console.error(`GitHub API Response Status: ${response.status}`);
144
- console.error(`GitHub API Response Body: ${errorBody}`);
145
- throw new Error(`GitHub API error: ${response.status} - ${errorBody}`);
146
- }
147
- console.log(`Gist ${ENV_GIST_ID} updated successfully with tunnel URL: ${tunnelUrlToSave}`);
148
- } catch (error) {
149
- console.error('Error updating Gist:', error.message); // Выводим только сообщение об ошибке для краткости
150
- }
151
  }
152
 
153
-
154
- // --- Маршруты Express ---
155
- // ... (маршруты без изменений) ...
156
- app.get('/', (req, res) => {
157
- res.send(`Telegram API Tools. Tunnel: ${currentTunnelUrl || 'pending...'}. Stats: ${currentTunnelUrl}/stats. File base: ${currentTunnelUrl}/file/`);
158
  });
159
-
160
- app.get('/stats', async (req, res) => {
161
  const stats = await getDirectoryStats(TELEGRAM_DATA_DIR);
162
- let ttlCleanupResult = { message: "TTL cleanup not run or disabled." };
163
  if (req.query.run_ttl_now === 'true' && FILES_TTL_HOURS > 0) {
164
- ttlCleanupResult = await cleanupOldFiles(TELEGRAM_DATA_DIR, FILES_TTL_HOURS);
165
  }
166
- res.json({
167
- directory: TELEGRAM_DATA_DIR,
168
- ...stats,
169
- files_ttl_hours: FILES_TTL_HOURS > 0 ? FILES_TTL_HOURS : 'disabled',
170
- ttl_cleanup_on_this_request: ttlCleanupResult
171
- });
172
  });
173
-
174
- app.get('/file/:filepath(*)', (req, res) => {
175
  const relativePath = req.params.filepath;
176
- if (!relativePath || relativePath.includes('..')) {
177
- return res.status(400).send('Invalid file path.');
178
- }
179
-
180
  const absoluteRequestedPath = path.normalize(path.join(TELEGRAM_DATA_DIR, relativePath));
181
-
182
- if (!absoluteRequestedPath.startsWith(path.resolve(TELEGRAM_DATA_DIR))) {
183
- return res.status(403).send('Forbidden: Access outside designated directory is not allowed.');
184
- }
185
-
186
- if (fssync.existsSync(absoluteRequestedPath)) {
187
- const stats = fssync.statSync(absoluteRequestedPath);
188
- if (stats.isFile()) {
189
- res.sendFile(absoluteRequestedPath, (err) => {
190
- if (err) {
191
- console.error(`Error sending file ${absoluteRequestedPath}:`, err);
192
- if (!res.headersSent) {
193
- res.status(500).send('Error sending file.');
194
- }
195
- }
196
- });
197
- } else {
198
- res.status(404).send('Path is not a file.');
199
- }
200
- } else {
201
- res.status(404).send('File not found.');
202
- }
203
  });
204
-
205
- // --- Запуск сервера и тунн��ля ---
206
- // ... (логика запуска сервера и туннеля без изменений) ...
207
- app.listen(EXPRESS_PORT, () => {
208
- console.log(`Express server (for tools) listening on port ${EXPRESS_PORT}`);
209
- console.log(`Attempting to start localhost.run tunnel for port ${EXPRESS_PORT}...`);
210
-
211
- const tunnelProcess = spawn('ssh', [
212
- '-R', `80:localhost:${EXPRESS_PORT}`,
213
- '-o', 'StrictHostKeyChecking=no',
214
- '-o', 'UserKnownHostsFile=/dev/null',
215
- '-o', 'ServerAliveInterval=60',
216
- '-o', 'ExitOnForwardFailure=yes',
217
- '-o', 'LogLevel=ERROR',
218
- 'nokey@localhost.run'
219
- ]);
220
-
221
- tunnelProcess.stdout.on('data', (data) => {
222
- const output = data.toString();
223
- const urlMatch = output.match(/https?:\/\/[a-zA-Z0-9-]+\.(lhr\.life|lhr\.run)/);
224
- if (urlMatch && urlMatch[0] !== currentTunnelUrl) {
225
- currentTunnelUrl = urlMatch[0];
226
- console.log(`>>> Tools Tunnel active: ${currentTunnelUrl}`);
227
- updateEnvGistInGithub(currentTunnelUrl).catch(console.error);
228
  }
229
- if (!urlMatch) {
230
- console.log(`localhost.run stdout: ${output}`);
231
- }
232
- });
233
-
234
- tunnelProcess.stderr.on('data', (data) => {
235
- console.error(`localhost.run stderr: ${data.toString()}`);
236
- });
237
 
238
- tunnelProcess.on('close', (code) => {
239
- console.log(`localhost.run tunnel process exited with code ${code}`);
240
- currentTunnelUrl = '';
241
- updateEnvGistInGithub('Tunnel closed or failed.').catch(console.error);
242
- });
243
 
244
- tunnelProcess.on('error', (err) => {
245
- console.error('Failed to start localhost.run tunnel process:', err);
246
- currentTunnelUrl = '';
247
- updateEnvGistInGithub('Tunnel failed to start.').catch(console.error);
248
- });
 
249
 
250
- updateEnvGistInGithub('Tunnel URL pending...').catch(console.error);
251
 
252
  if (FILES_TTL_HOURS > 0) {
253
  const ttlIntervalMs = FILES_TTL_HOURS * 60 * 60 * 1000;
254
  console.log(`[TTL] Scheduling cleanup every ${FILES_TTL_HOURS} hours.`);
255
  setTimeout(() => {
256
  cleanupOldFiles(TELEGRAM_DATA_DIR, FILES_TTL_HOURS).catch(console.error);
257
- setInterval(() => {
258
- cleanupOldFiles(TELEGRAM_DATA_DIR, FILES_TTL_HOURS).catch(console.error);
259
- }, ttlIntervalMs);
260
- }, 5 * 60 * 1000);
261
  }
262
  });
263
 
264
- function gracefulShutdown() {
265
- console.log('Received shutdown signal. Closing server...');
266
- process.exit(0);
267
- }
268
  process.on('SIGINT', gracefulShutdown);
269
  process.on('SIGTERM', gracefulShutdown);
 
2
  import fs from 'fs/promises';
3
  import fssync from 'fs';
4
  import path from 'path';
 
5
  import fetch from 'node-fetch';
6
  import { glob } from 'glob';
7
+ import { createProxyMiddleware } from 'http-proxy-middleware';
8
 
9
  // --- Конфигурация ---
10
+ const APP_PORT = parseInt(process.env.PORT || '7860', 10); // HF устанавливает PORT
11
+ const INTERNAL_TELEGRAM_API_PORT = parseInt(process.env.INTERNAL_TELEGRAM_API_PORT || '8081', 10);
12
  const TELEGRAM_DATA_DIR = process.env.TELEGRAM_DATA_DIR || '/var/lib/telegram-bot-api';
13
  const FILES_TTL_HOURS = parseInt(process.env.FILES_TTL || '-1', 10);
14
+ const PROXY_TELEGRAM_PATH_PREFIX = process.env.PROXY_TELEGRAM_PATH_PREFIX || '/telegram';
15
+
16
 
17
  const GITHUB_USERNAME = process.env.GITHUB_USERNAME || '';
18
  const GITHUB_TOKEN = process.env.GITHUB_TOKEN || '';
19
  const ENV_GIST_ID = process.env.ENV_GIST_ID || '';
20
 
 
 
21
  const app = express();
22
 
23
+ // --- Вспомогательные функции (formatBytes, getDirectoryStats, cleanupOldFiles) ---
24
  function formatBytes(bytes, decimals = 2) {
25
  if (bytes === 0) return '0 Bytes';
26
  const k = 1024;
 
38
  try {
39
  const stats = await fs.stat(file);
40
  totalSize += stats.size;
41
+ } catch (e) { /* Игнор */ }
 
 
42
  }
43
  return {
44
  fileCount: files.length,
 
58
  console.log(`[TTL] Starting cleanup for files older than ${ttlHours} hours in ${dirPath}`);
59
  const now = Date.now();
60
  const ttlMs = ttlHours * 60 * 60 * 1000;
61
+ let processed = 0, deleted = 0, errors = 0;
 
 
 
62
  try {
63
  const files = await glob(`${dirPath}/**/*`, { nodir: true, dot: true, stat: true, withFileTypes: false });
 
64
  for (const file of files) {
65
  processed++;
66
  try {
67
+ const stats = await fs.stat(file);
68
  if (stats.isFile()) {
69
+ if ((now - stats.mtimeMs) > ttlMs) {
 
70
  await fs.unlink(file);
71
  deleted++;
 
72
  }
73
  }
74
+ } catch (e) { errors++; console.error(`[TTL] Error processing file ${file}:`, e.message); }
 
 
 
75
  }
76
  } catch (globError) {
77
  console.error(`[TTL] Error during glob search:`, globError);
78
  return { processed, deleted, errors: errors + 1, message: `Glob error: ${globError.message}` };
79
  }
 
80
  const result = { processed, deleted, errors, message: `Cleanup completed. Processed: ${processed}, Deleted: ${deleted}, Errors: ${errors}` };
81
  console.log(`[TTL] ${result.message}`);
82
  return result;
83
  }
84
 
 
85
  // --- Обновление Gist ---
86
+ async function updateEnvGistInGithub() {
87
  if (!GITHUB_USERNAME || !GITHUB_TOKEN || !ENV_GIST_ID) {
88
  console.warn('Gist update skipped: GITHUB_USERNAME, GITHUB_TOKEN, or ENV_GIST_ID is not set.');
89
  return;
90
  }
91
  try {
92
  const spaceId = process.env.SPACE_ID || 'N/A';
93
+ const spaceHost = process.env.SPACE_HOST || 'N/A'; // e.g., your-user-your-space.hf.space
94
+ const publicSpaceUrl = spaceHost ? `https://${spaceHost}` : 'N/A';
 
 
95
 
96
+ const gistFilename = `hf_space_info.json`;
97
  const content = {
98
  last_updated: new Date().toISOString(),
99
  space_id: spaceId,
100
+ public_space_url: publicSpaceUrl,
101
+ tools_path_stats: `${publicSpaceUrl}/tools/stats`,
102
+ tools_path_file_base: `${publicSpaceUrl}/tools/file/`,
103
+ telegram_api_proxy_path: `${publicSpaceUrl}${PROXY_TELEGRAM_PATH_PREFIX}/bot<YOUR_BOT_TOKEN>/<METHOD>`,
104
+ app_main_port: APP_PORT,
105
+ internal_telegram_api_port: INTERNAL_TELEGRAM_API_PORT,
106
  files_ttl_hours: FILES_TTL_HOURS > 0 ? FILES_TTL_HOURS : 'disabled',
107
  };
 
108
  const gistData = {
109
+ description: `Hugging Face Space Info - ${spaceId}`,
110
+ files: { [gistFilename]: { content: JSON.stringify(content, null, 2) } },
 
 
 
 
111
  };
 
 
112
  const response = await fetch(`https://api.github.com/gists/${ENV_GIST_ID}`, {
113
  method: 'PATCH',
114
  headers: {
115
+ 'Authorization': `token ${GITHUB_TOKEN}`, 'Accept': 'application/vnd.github.v3+json',
116
+ 'User-Agent': 'HFSpaceTgAPIProxyTools', 'Content-Type': 'application/json',
 
 
117
  },
118
  body: JSON.stringify(gistData),
119
  });
120
+ if (!response.ok) throw new Error(`GitHub API error: ${response.status} ${await response.text()}`);
121
+ console.log(`Gist ${ENV_GIST_ID} updated successfully.`);
122
+ } catch (error) { console.error('Error updating Gist:', error.message); }
 
 
 
 
 
 
 
 
123
  }
124
 
125
+ // --- Маршруты для инструментов ---
126
+ const toolsRouter = express.Router();
127
+ toolsRouter.get('/', (req, res) => {
128
+ const spaceHost = process.env.SPACE_HOST || 'your-space.hf.space';
129
+ res.send(`Telegram API Proxy & Tools. Stats: /tools/stats. File base: /tools/file/. Telegram API via: ${PROXY_TELEGRAM_PATH_PREFIX}/bot<TOKEN>/...`);
130
  });
131
+ toolsRouter.get('/stats', async (req, res) => {
 
132
  const stats = await getDirectoryStats(TELEGRAM_DATA_DIR);
133
+ let ttlResult = { message: "TTL cleanup not run or disabled." };
134
  if (req.query.run_ttl_now === 'true' && FILES_TTL_HOURS > 0) {
135
+ ttlResult = await cleanupOldFiles(TELEGRAM_DATA_DIR, FILES_TTL_HOURS);
136
  }
137
+ res.json({ directory: TELEGRAM_DATA_DIR, ...stats, files_ttl_hours: FILES_TTL_HOURS > 0 ? FILES_TTL_HOURS : 'disabled', ttl_cleanup_on_this_request: ttlResult });
 
 
 
 
 
138
  });
139
+ toolsRouter.get('/file/:filepath(*)', (req, res) => {
 
140
  const relativePath = req.params.filepath;
141
+ if (!relativePath || relativePath.includes('..')) return res.status(400).send('Invalid file path.');
 
 
 
142
  const absoluteRequestedPath = path.normalize(path.join(TELEGRAM_DATA_DIR, relativePath));
143
+ if (!absoluteRequestedPath.startsWith(path.resolve(TELEGRAM_DATA_DIR))) return res.status(403).send('Forbidden.');
144
+ if (fssync.existsSync(absoluteRequestedPath) && fssync.statSync(absoluteRequestedPath).isFile()) {
145
+ res.sendFile(absoluteRequestedPath, err => {
146
+ if (err && !res.headersSent) res.status(500).send('Error sending file.');
147
+ });
148
+ } else res.status(404).send('File not found.');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  });
150
+ app.use('/tools', toolsRouter);
151
+
152
+ // --- Обратный прокси для Telegram Bot API ---
153
+ app.use(PROXY_TELEGRAM_PATH_PREFIX, createProxyMiddleware({
154
+ target: `http://localhost:${INTERNAL_TELEGRAM_API_PORT}`,
155
+ changeOrigin: true,
156
+ pathRewrite: (path, req) => {
157
+ // Убираем префикс PROXY_TELEGRAM_PATH_PREFIX из пути перед отправкой на Telegram API
158
+ // Например, /telegram/bot<TOKEN>/getMe -> /bot<TOKEN>/getMe
159
+ return path.startsWith(PROXY_TELEGRAM_PATH_PREFIX) ? path.substring(PROXY_TELEGRAM_PATH_PREFIX.length) : path;
160
+ },
161
+ onProxyReq: (proxyReq, req, res) => {
162
+ console.log(`[Proxy] Request to Telegram API: ${req.method} ${proxyReq.path}`);
163
+ },
164
+ onError: (err, req, res) => {
165
+ console.error('[Proxy] Error:', err);
166
+ if (!res.headersSent) {
167
+ res.writeHead(500, { 'Content-Type': 'application/json' });
 
 
 
 
 
 
168
  }
169
+ res.end(JSON.stringify({ error: 'Proxy Error', message: err.message }));
170
+ }
171
+ }));
 
 
 
 
 
172
 
173
+ // --- Корневой маршрут ---
174
+ app.get('/', (req, res) => {
175
+ res.redirect('/tools');
176
+ });
 
177
 
178
+ // --- Запуск сервера ---
179
+ app.listen(APP_PORT, () => {
180
+ console.log(`Main app (Reverse Proxy & Tools) listening on port ${APP_PORT}`);
181
+ console.log(`Telegram Bot API (internal) should be on port ${INTERNAL_TELEGRAM_API_PORT}`);
182
+ console.log(`Tools available at /tools`);
183
+ console.log(`Telegram API proxied from ${PROXY_TELEGRAM_PATH_PREFIX}/`);
184
 
185
+ updateEnvGistInGithub().catch(console.error);
186
 
187
  if (FILES_TTL_HOURS > 0) {
188
  const ttlIntervalMs = FILES_TTL_HOURS * 60 * 60 * 1000;
189
  console.log(`[TTL] Scheduling cleanup every ${FILES_TTL_HOURS} hours.`);
190
  setTimeout(() => {
191
  cleanupOldFiles(TELEGRAM_DATA_DIR, FILES_TTL_HOURS).catch(console.error);
192
+ setInterval(() => cleanupOldFiles(TELEGRAM_DATA_DIR, FILES_TTL_HOURS).catch(console.error), ttlIntervalMs);
193
+ }, 5 * 60 * 1000);
 
 
194
  }
195
  });
196
 
197
+ function gracefulShutdown() { console.log('Received shutdown signal. Exiting...'); process.exit(0); }
 
 
 
198
  process.on('SIGINT', gracefulShutdown);
199
  process.on('SIGTERM', gracefulShutdown);