soxogvv commited on
Commit
a74b29d
Β·
verified Β·
1 Parent(s): 96bd928

Upload 2 files

Browse files
Files changed (2) hide show
  1. Dockerfile +12 -1
  2. app.js +106 -10
Dockerfile CHANGED
@@ -16,8 +16,19 @@ RUN apt-get update && \
16
  mediainfo \
17
  screen \
18
  nano \
 
19
  && rm -rf /var/lib/apt/lists/*
20
 
 
 
 
 
 
 
 
 
 
 
21
  # ── Pin a stable machine-id ───────────────────────────────────────────────────
22
  RUN echo "d8904b4d338adf83688caac869f64c0b" > /etc/machine-id && \
23
  mkdir -p /var/lib/dbus && \
@@ -47,4 +58,4 @@ COPY . .
47
  # ── Runtime ───────────────────────────────────────────────────────────────────
48
  EXPOSE 7860
49
  ENV PORT=7860
50
- CMD ["node", "app.js"]
 
16
  mediainfo \
17
  screen \
18
  nano \
19
+ openssh-server \
20
  && rm -rf /var/lib/apt/lists/*
21
 
22
+ # ── Install bore (TCP tunnel β€” exposes SSH publicly for Termius) ──────────────
23
+ RUN wget -q "https://github.com/ekzhang/bore/releases/download/v0.5.0/bore-v0.5.0-x86_64-unknown-linux-musl.tar.gz" \
24
+ -O /tmp/bore.tar.gz && \
25
+ tar -xzf /tmp/bore.tar.gz -C /usr/local/bin bore && \
26
+ chmod +x /usr/local/bin/bore && \
27
+ rm /tmp/bore.tar.gz
28
+
29
+ # ── Prepare SSH runtime dirs ──────────────────────────────────────────────────
30
+ RUN mkdir -p /run/sshd /root/.ssh
31
+
32
  # ── Pin a stable machine-id ───────────────────────────────────────────────────
33
  RUN echo "d8904b4d338adf83688caac869f64c0b" > /etc/machine-id && \
34
  mkdir -p /var/lib/dbus && \
 
58
  # ── Runtime ───────────────────────────────────────────────────────────────────
59
  EXPOSE 7860
60
  ENV PORT=7860
61
+ CMD ["node", "app.js"]
app.js CHANGED
@@ -1,5 +1,5 @@
1
  import express from 'express';
2
- import { spawn } from 'child_process';
3
  import crypto from 'crypto';
4
  import fs from 'fs';
5
  import os from 'os';
@@ -17,19 +17,22 @@ if (!SECRET_KEY) {
17
  process.exit(1);
18
  }
19
 
 
 
 
 
 
 
 
20
  app.use(express.json());
21
  app.use(express.static(path.join(__dirname, 'public')));
22
 
23
  // ── Session store ─────────────────────────────────────────────────────────────
24
  const sessions = new Set();
25
 
26
- // Persistent auto-token derived from SECRET_KEY β€” survives restarts on the
27
- // client side (stored in sessionStorage) and is always valid server-side.
28
  const AUTO_TOKEN = crypto.createHash('sha256').update(`auto:${SECRET_KEY}`).digest('hex');
29
  sessions.add(AUTO_TOKEN);
30
 
31
- // Unauthenticated endpoint β€” lets the frontend self-authenticate after a
32
- // container restart without the user having to type the key again.
33
  app.get('/api/auto-token', (_req, res) => {
34
  res.json({ token: AUTO_TOKEN });
35
  });
@@ -259,7 +262,6 @@ function startSyncPy() {
259
  syncProc.on('exit', (code, signal) => {
260
  console.warn(`[sync] sync.py exited β€” code=${code ?? '?'}, signal=${signal ?? 'none'}`);
261
  syncProc = null;
262
- // Auto-restart after 10s if it crashes
263
  setTimeout(startSyncPy, 10_000);
264
  });
265
  }
@@ -270,6 +272,99 @@ function stopSyncPy() {
270
  syncProc = null;
271
  }
272
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  // ── SSE stream ─────────────────────────────────────────────────────────────────
274
  app.get('/api/stream', requireAuth, (req, res) => {
275
  res.setHeader('Content-Type', 'text/event-stream');
@@ -351,10 +446,11 @@ app.get('/api/shellular/qr-data', requireAuth, (_req, res) => {
351
  // ��─ Start ──────────────────────────────────────────────────────────────────────
352
  app.listen(PORT, '0.0.0.0', () => {
353
  console.log(`Shellular Web UI β†’ http://0.0.0.0:${PORT}`);
354
- startSyncPy(); // 🐍 Launch sync.py on server start
355
- startShellular(); // πŸš€ Auto-start shellular on boot so QR is ready immediately
 
356
  });
357
 
358
  // ── Graceful shutdown ──────────────────────────────────────────────────────────
359
- process.on('SIGTERM', () => { stopShellular(); stopSyncPy(); });
360
- process.on('SIGINT', () => { stopShellular(); stopSyncPy(); });
 
1
  import express from 'express';
2
+ import { spawn, execSync } from 'child_process';
3
  import crypto from 'crypto';
4
  import fs from 'fs';
5
  import os from 'os';
 
17
  process.exit(1);
18
  }
19
 
20
+ // SSH password: deterministic from SECRET_KEY β€” same across restarts, easy to copy
21
+ const SSH_PASSWORD = crypto
22
+ .createHash('sha256')
23
+ .update(`ssh:${SECRET_KEY}`)
24
+ .digest('hex')
25
+ .slice(0, 20);
26
+
27
  app.use(express.json());
28
  app.use(express.static(path.join(__dirname, 'public')));
29
 
30
  // ── Session store ─────────────────────────────────────────────────────────────
31
  const sessions = new Set();
32
 
 
 
33
  const AUTO_TOKEN = crypto.createHash('sha256').update(`auto:${SECRET_KEY}`).digest('hex');
34
  sessions.add(AUTO_TOKEN);
35
 
 
 
36
  app.get('/api/auto-token', (_req, res) => {
37
  res.json({ token: AUTO_TOKEN });
38
  });
 
262
  syncProc.on('exit', (code, signal) => {
263
  console.warn(`[sync] sync.py exited β€” code=${code ?? '?'}, signal=${signal ?? 'none'}`);
264
  syncProc = null;
 
265
  setTimeout(startSyncPy, 10_000);
266
  });
267
  }
 
272
  syncProc = null;
273
  }
274
 
275
+ // ── SSH server setup ──────────────────────────────────────────────────────────
276
+ function setupSSH() {
277
+ try {
278
+ // Set root password
279
+ execSync(`echo "root:${SSH_PASSWORD}" | chpasswd`, { stdio: 'ignore' });
280
+ console.log('[ssh] root password set');
281
+
282
+ // Allow root + password login
283
+ fs.mkdirSync('/etc/ssh/sshd_config.d', { recursive: true });
284
+ fs.writeFileSync('/etc/ssh/sshd_config.d/99-termius.conf', [
285
+ 'PermitRootLogin yes',
286
+ 'PasswordAuthentication yes',
287
+ 'ChallengeResponseAuthentication no',
288
+ 'UsePAM no',
289
+ 'PrintMotd no',
290
+ ].join('\n') + '\n', 'utf-8');
291
+
292
+ // Generate host keys (no-op if already present)
293
+ execSync('ssh-keygen -A', { stdio: 'ignore' });
294
+
295
+ // Launch sshd
296
+ const sshd = spawn('/usr/sbin/sshd', ['-D', '-e'], {
297
+ detached: true,
298
+ stdio: 'ignore',
299
+ });
300
+ sshd.unref();
301
+ console.log('[ssh] sshd started, pid:', sshd.pid);
302
+
303
+ // Give sshd a moment then open the bore tunnel
304
+ setTimeout(startBore, 2000);
305
+ } catch (err) {
306
+ console.error('[ssh] setup error:', err.message);
307
+ }
308
+ }
309
+
310
+ // ── Bore tunnel (exposes SSH port to the internet) ────────────────────────────
311
+ let boreProc = null;
312
+ let boreHost = null;
313
+ let borePort = null;
314
+
315
+ function startBore() {
316
+ if (boreProc) return;
317
+ console.log('[bore] starting tunnel…');
318
+
319
+ boreProc = spawn('bore', ['local', '22', '--to', 'bore.pub'], {
320
+ stdio: ['ignore', 'pipe', 'pipe'],
321
+ });
322
+
323
+ const onData = (chunk) => {
324
+ const text = chunk.toString();
325
+ console.log('[bore]', text.trim());
326
+ // bore prints: "… Listening at bore.pub:NNNNN"
327
+ const m = text.match(/bore\.pub:(\d+)/i);
328
+ if (m) {
329
+ boreHost = 'bore.pub';
330
+ borePort = parseInt(m[1], 10);
331
+ console.log(`[bore] tunnel ready β†’ ${boreHost}:${borePort}`);
332
+ }
333
+ };
334
+
335
+ boreProc.stdout.on('data', onData);
336
+ boreProc.stderr.on('data', onData);
337
+
338
+ boreProc.on('error', (err) => {
339
+ console.error('[bore] error:', err.message);
340
+ boreProc = null; boreHost = null; borePort = null;
341
+ setTimeout(startBore, 15_000);
342
+ });
343
+
344
+ boreProc.on('exit', (code, signal) => {
345
+ console.warn(`[bore] exited code=${code} signal=${signal} β€” restarting in 10 s`);
346
+ boreProc = null; boreHost = null; borePort = null;
347
+ setTimeout(startBore, 10_000);
348
+ });
349
+ }
350
+
351
+ function stopBore() {
352
+ if (!boreProc) return;
353
+ boreProc.kill('SIGTERM');
354
+ boreProc = null; boreHost = null; borePort = null;
355
+ }
356
+
357
+ // ── SSH info endpoint (used by frontend to show Termius credentials) ──────────
358
+ app.get('/api/ssh-info', requireAuth, (_req, res) => {
359
+ res.json({
360
+ ready: !!(boreHost && borePort),
361
+ host: boreHost,
362
+ port: borePort,
363
+ username: 'root',
364
+ password: SSH_PASSWORD,
365
+ });
366
+ });
367
+
368
  // ── SSE stream ─────────────────────────────────────────────────────────────────
369
  app.get('/api/stream', requireAuth, (req, res) => {
370
  res.setHeader('Content-Type', 'text/event-stream');
 
446
  // ��─ Start ──────────────────────────────────────────────────────────────────────
447
  app.listen(PORT, '0.0.0.0', () => {
448
  console.log(`Shellular Web UI β†’ http://0.0.0.0:${PORT}`);
449
+ startSyncPy(); // 🐍 Sync to HF dataset
450
+ startShellular(); // πŸš€ Shellular for QR access
451
+ setupSSH(); // πŸ”’ SSH server + bore tunnel for Termius
452
  });
453
 
454
  // ── Graceful shutdown ──────────────────────────────────────────────────────────
455
+ process.on('SIGTERM', () => { stopShellular(); stopSyncPy(); stopBore(); });
456
+ process.on('SIGINT', () => { stopShellular(); stopSyncPy(); stopBore(); });