Gioobc commited on
Commit
5273cd2
·
verified ·
1 Parent(s): 9e86ee2

fast server for auditory of acts

Browse files
Files changed (5) hide show
  1. Dockerfile +19 -0
  2. index.js +78 -0
  3. lib/remote.js +86 -0
  4. package.json +11 -0
  5. public/index.html +61 -0
Dockerfile ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:20
2
+
3
+ # Instalamos unzip para los paquetes
4
+ RUN apt-get update && apt-get install -y unzip && rm -rf /var/lib/apt/lists/*
5
+
6
+ WORKDIR /app
7
+
8
+ COPY package*.json ./
9
+ RUN npm install
10
+
11
+ COPY . .
12
+
13
+ # Creamos las carpetas necesarias con permisos
14
+ RUN mkdir -p storage temp && chmod 777 storage temp
15
+
16
+ EXPOSE 7860
17
+ ENV PORT=7860
18
+
19
+ CMD ["node", "index.js"]
index.js ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import 'dotenv/config';
2
+ import express from 'express';
3
+ import path from 'path';
4
+ import fs from 'fs';
5
+ import { fileURLToPath } from 'url';
6
+ import { execSync } from 'child_process';
7
+ import RemoteFetcher from './lib/remote.js';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ const app = express();
13
+ const PORT = process.env.PORT || 7861; // Puerto diferente para no chocar
14
+
15
+ const STORAGE_DIR = path.join(__dirname, 'storage');
16
+ const TEMP_DIR = path.join(__dirname, 'temp');
17
+
18
+ if (!fs.existsSync(STORAGE_DIR)) fs.mkdirSync(STORAGE_DIR, { recursive: true });
19
+ if (!fs.existsSync(TEMP_DIR)) fs.mkdirSync(TEMP_DIR, { recursive: true });
20
+
21
+ app.use(express.static('public'));
22
+ app.use('/view', express.static(STORAGE_DIR));
23
+
24
+ // Endpoint ultra simple: Listar archivos en disco
25
+ app.get('/api/files', (req, res) => {
26
+ const files = fs.readdirSync(STORAGE_DIR).filter(f => !f.startsWith('.'));
27
+ res.json(files);
28
+ });
29
+
30
+ app.listen(PORT, () => {
31
+ console.log(`🚀 Servidor SIMPLE corriendo en puerto ${PORT}`);
32
+ startSimpleWorker();
33
+ });
34
+
35
+ async function startSimpleWorker() {
36
+ const config = {
37
+ host: process.env.REMOTE_HOST,
38
+ username: process.env.REMOTE_USER,
39
+ password: process.env.REMOTE_PASS,
40
+ port: parseInt(process.env.REMOTE_PORT || 22)
41
+ };
42
+
43
+ const fetcher = new RemoteFetcher(config);
44
+ try {
45
+ await fetcher.connect();
46
+ console.log('✅ Conexión SSH lista.');
47
+
48
+ const remoteRoot = '/home/user110426@vi/ACTAS';
49
+ const regions = await fetcher.listDirectories(remoteRoot);
50
+
51
+ for (const region of regions) {
52
+ console.log(`🌍 Entrando a: ${region}`);
53
+ const regionPath = path.posix.join(remoteRoot, region);
54
+ const zips = await fetcher.listFiles(regionPath, '.zip');
55
+
56
+ for (const zipName of zips) {
57
+ const localZipPath = path.join(TEMP_DIR, zipName);
58
+ const remoteZipPath = path.posix.join(regionPath, zipName);
59
+
60
+ console.log(`📦 Descargando ${zipName}...`);
61
+ await fetcher.fetchFile(remoteZipPath, localZipPath);
62
+
63
+ console.log(`📂 Descomprimiendo ${zipName}...`);
64
+ try {
65
+ execSync(`unzip -o "${localZipPath}" -d "${STORAGE_DIR}"`);
66
+ } catch (e) {
67
+ console.log(`❌ Error unzip: ${e.message}`);
68
+ }
69
+
70
+ // Borramos el zip para no llenar el disco
71
+ fs.unlinkSync(localZipPath);
72
+ console.log(`✨ ${zipName} listo.`);
73
+ }
74
+ }
75
+ } catch (err) {
76
+ console.error(`❌ Error SimpleWorker: ${err.message}`);
77
+ }
78
+ }
lib/remote.js ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Client } from 'ssh2';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ export default class RemoteFetcher {
6
+ constructor(config) {
7
+ this.config = {
8
+ host: config.host,
9
+ port: config.port || 22,
10
+ username: config.username,
11
+ password: config.password,
12
+ readyTimeout: 40000,
13
+ keepaliveInterval: 10000
14
+ };
15
+ this.conn = new Client();
16
+ }
17
+
18
+ connect() {
19
+ return new Promise((resolve, reject) => {
20
+ this.conn.on('ready', () => {
21
+ this.conn.sftp((err, sftp) => {
22
+ if (err) return reject(err);
23
+ this.sftp = sftp;
24
+ resolve();
25
+ });
26
+ })
27
+ .on('error', (err) => reject(err))
28
+ .connect(this.config);
29
+ });
30
+ }
31
+
32
+ // Volvemos a fastGet pero con configuración optimizada para velocidad/estabilidad
33
+ async fetchFile(remotePath, localPath, onProgress) {
34
+ return new Promise((resolve, reject) => {
35
+ this.sftp.fastGet(remotePath, localPath, {
36
+ concurrency: 6, // 6 descargas en paralelo para mayor velocidad
37
+ chunkSize: 32768, // Bloques de 32KB
38
+ step: (transferred, chunk, total) => {
39
+ if (onProgress) onProgress(transferred, total);
40
+ }
41
+ }, (err) => {
42
+ if (err) return reject(err);
43
+ resolve();
44
+ });
45
+ });
46
+ }
47
+
48
+ async listFiles(remoteDir, extension = '') {
49
+ return new Promise((resolve, reject) => {
50
+ this.sftp.readdir(remoteDir, (err, list) => {
51
+ if (err) return reject(err);
52
+ const files = list
53
+ .filter(f => f.attrs.isFile())
54
+ .map(f => f.filename);
55
+
56
+ if (extension) {
57
+ resolve(files.filter(f => f.toLowerCase().endsWith(extension.toLowerCase())));
58
+ } else {
59
+ resolve(files);
60
+ }
61
+ });
62
+ });
63
+ }
64
+
65
+ async listDirectories(remoteDir) {
66
+ return new Promise((resolve, reject) => {
67
+ this.sftp.readdir(remoteDir, (err, list) => {
68
+ if (err) return reject(err);
69
+ resolve(list.filter(f => f.attrs.isDirectory() && f.filename !== '.' && f.filename !== '..').map(f => f.filename));
70
+ });
71
+ });
72
+ }
73
+
74
+ async getFileStats(remotePath) {
75
+ return new Promise((resolve, reject) => {
76
+ this.sftp.stat(remotePath, (err, stats) => {
77
+ if (err) return reject(err);
78
+ resolve(stats);
79
+ });
80
+ });
81
+ }
82
+
83
+ disconnect() {
84
+ this.conn.end();
85
+ }
86
+ }
package.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "votolibre-simpleserver",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "index.js",
6
+ "dependencies": {
7
+ "dotenv": "^16.4.5",
8
+ "express": "^4.19.2",
9
+ "ssh2": "^1.15.0"
10
+ }
11
+ }
public/index.html ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="es">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>VotoLibre | Fast Audit</title>
6
+ <style>
7
+ body { font-family: sans-serif; padding: 20px; background: #f4f4f9; color: #333; }
8
+ .container { max-width: 800px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
9
+ h1 { color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px; }
10
+ input { width: 100%; padding: 10px; margin: 20px 0; border: 1px solid #ddd; border-radius: 5px; }
11
+ ul { list-style: none; padding: 0; }
12
+ li { padding: 10px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; }
13
+ li:hover { background: #f9f9f9; }
14
+ a { color: #3498db; text-decoration: none; font-weight: bold; }
15
+ a:hover { text-decoration: underline; }
16
+ .count { font-size: 0.9em; color: #666; }
17
+ </style>
18
+ </head>
19
+ <body>
20
+ <div class="container">
21
+ <h1>VotoLibre FastTrack Audit</h1>
22
+ <p>Lista simple de actas descargadas en tiempo real.</p>
23
+ <div class="count">Actas disponibles: <span id="count">0</span></div>
24
+ <input type="text" id="search" placeholder="Buscar acta por número..." onkeyup="filter()">
25
+ <ul id="list">
26
+ <li>Cargando archivos...</li>
27
+ </ul>
28
+ </div>
29
+
30
+ <script>
31
+ let allFiles = [];
32
+ async function load() {
33
+ try {
34
+ const res = await fetch('/api/files');
35
+ allFiles = await res.json();
36
+ document.getElementById('count').innerText = allFiles.length;
37
+ render(allFiles);
38
+ } catch (e) {}
39
+ }
40
+
41
+ function render(list) {
42
+ const ul = document.getElementById('list');
43
+ ul.innerHTML = list.map(f => `
44
+ <li>
45
+ <span>${f}</span>
46
+ <a href="/view/${f}" target="_blank">Abrir Imagen</a>
47
+ </li>
48
+ `).join('');
49
+ }
50
+
51
+ function filter() {
52
+ const q = document.getElementById('search').value.toLowerCase();
53
+ const filtered = allFiles.filter(f => f.toLowerCase().includes(q));
54
+ render(filtered);
55
+ }
56
+
57
+ setInterval(load, 5000);
58
+ load();
59
+ </script>
60
+ </body>
61
+ </html>