Download app.js from Vvbbnnn/vps: direct link, hf CLI and curl.
- Browser
- Download file 15.7 kB
-
https://huggingface.co/spaces/Vvbbnnn/vps/resolve/main/app.js
- Command line
-
hf download hf://spaces/Vvbbnnn/vps/app.js
-
curl -L -o app.js https://huggingface.co/spaces/Vvbbnnn/vps/resolve/main/app.js
15.7 kB
| import express from 'express'; | |
| import { spawn, execSync } from 'child_process'; | |
| import crypto from 'crypto'; | |
| import fs from 'fs'; | |
| import os from 'os'; | |
| import path from 'path'; | |
| import { fileURLToPath } from 'url'; | |
| const __dirname = path.dirname(fileURLToPath(import.meta.url)); | |
| const app = express(); | |
| const PORT = process.env.PORT || 7860; | |
| const SECRET_KEY = process.env.SECRET_KEY | |
| if (!SECRET_KEY) { | |
| console.error('[ERROR] SECRET_KEY environment variable is not set. Exiting.'); | |
| process.exit(1); | |
| } | |
| // SSH password: deterministic from SECRET_KEY β same across restarts, easy to copy | |
| const SSH_PASSWORD = crypto | |
| .createHash('sha256') | |
| .update(`ssh:${SECRET_KEY}`) | |
| .digest('hex') | |
| .slice(0, 20); | |
| app.use(express.json()); | |
| app.use(express.static(path.join(__dirname, 'public'))); | |
| // ββ Session store βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const sessions = new Set(); | |
| const AUTO_TOKEN = crypto.createHash('sha256').update(`auto:${SECRET_KEY}`).digest('hex'); | |
| sessions.add(AUTO_TOKEN); | |
| app.get('/api/auto-token', (_req, res) => { | |
| res.json({ token: AUTO_TOKEN }); | |
| }); | |
| // ββ ANSI stripper βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function stripAnsi(str) { | |
| // eslint-disable-next-line no-control-regex | |
| return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><~]/g, ''); | |
| } | |
| // ββ Pre-seed shellular config from env vars βββββββββββββββββββββββββββββββββββ | |
| function seedShellularConfig() { | |
| const hostId = process.env.SHELLULAR_HOST_ID; | |
| const keyB64 = process.env.SHELLULAR_KEY; | |
| const machineId = process.env.SHELLULAR_MACHINE_ID; | |
| if (!hostId || !keyB64 || !machineId) return; | |
| const shellularDir = path.join(os.homedir(), '.shellular'); | |
| const configFile = path.join(shellularDir, 'config.json'); | |
| const keyFile = path.join(shellularDir, `shellular-${machineId}.e2ee`); | |
| try { | |
| fs.mkdirSync(shellularDir, { recursive: true }); | |
| if (!fs.existsSync(configFile)) { | |
| fs.writeFileSync(configFile, JSON.stringify({ hostId, machineId }), 'utf-8'); | |
| console.log(`[shellular] seeded config: hostId=${hostId}`); | |
| } | |
| if (!fs.existsSync(keyFile)) { | |
| fs.writeFileSync(keyFile, Buffer.from(keyB64, 'base64'), { mode: 0o600 }); | |
| console.log(`[shellular] seeded key: ${keyFile}`); | |
| } | |
| } catch (err) { | |
| console.error('[shellular] failed to seed config:', err.message); | |
| } | |
| } | |
| seedShellularConfig(); | |
| // ββ Shellular machine-id helper βββββββββββββββββββββββββββββββββββββββββββββββ | |
| function getHashedMachineId() { | |
| try { | |
| const raw = fs.readFileSync('/etc/machine-id', 'utf-8').trim(); | |
| return crypto.createHash('sha256').update(raw).digest('hex'); | |
| } catch { | |
| return null; | |
| } | |
| } | |
| app.get('/api/shellular/machine-id', (_req, res) => { | |
| const id = getHashedMachineId(); | |
| id ? res.json({ machineId: id }) : res.status(500).json({ error: 'Cannot read machine-id' }); | |
| }); | |
| app.post('/api/shellular/seed-host', requireAuth, (req, res) => { | |
| const { hostId } = req.body || {}; | |
| if (!hostId || typeof hostId !== 'string' || !hostId.trim()) { | |
| return res.status(400).json({ error: 'hostId is required' }); | |
| } | |
| const machineId = getHashedMachineId(); | |
| if (!machineId) return res.status(500).json({ error: 'Cannot read machine-id' }); | |
| try { | |
| const shellularDir = path.join(os.homedir(), '.shellular'); | |
| fs.mkdirSync(shellularDir, { recursive: true }); | |
| fs.writeFileSync( | |
| path.join(shellularDir, 'config.json'), | |
| JSON.stringify({ hostId: hostId.trim(), machineId }, null, 2), | |
| 'utf-8' | |
| ); | |
| stopShellular(); | |
| outputBuffer = ''; | |
| broadcast({ type: 'clear' }); | |
| setTimeout(startShellular, 600); | |
| res.json({ ok: true }); | |
| } catch (err) { | |
| res.status(500).json({ error: err.message }); | |
| } | |
| }); | |
| // ββ Auth routes βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| app.post('/api/login', (req, res) => { | |
| const { key } = req.body; | |
| if (!key || key !== SECRET_KEY) { | |
| return res.status(401).json({ error: 'Invalid key' }); | |
| } | |
| const token = crypto.randomUUID(); | |
| sessions.add(token); | |
| res.json({ token }); | |
| }); | |
| app.post('/api/logout', (req, res) => { | |
| const token = req.headers.authorization?.split(' ')[1]; | |
| sessions.delete(token); | |
| res.json({ ok: true }); | |
| }); | |
| // ββ Auth middleware ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function requireAuth(req, res, next) { | |
| const token = req.headers.authorization?.split(' ')[1]; | |
| if (!token || !sessions.has(token)) { | |
| return res.status(401).json({ error: 'Unauthorized' }); | |
| } | |
| next(); | |
| } | |
| // ββ Shellular process management ββββββββββββββββββββββββββββββββββββββββββββββ | |
| let shellularProc = null; | |
| let outputBuffer = ''; | |
| const sseClients = new Set(); | |
| function send(res, payload) { | |
| res.write(`data: ${JSON.stringify(payload)}\n\n`); | |
| } | |
| function broadcast(payload) { | |
| const frame = `data: ${JSON.stringify(payload)}\n\n`; | |
| for (const client of sseClients) { | |
| client.write(frame); | |
| } | |
| } | |
| let retryTimer = null; | |
| function startShellular() { | |
| if (shellularProc || retryTimer) return; | |
| broadcast({ type: 'status', status: 'starting' }); | |
| shellularProc = spawn('shellular', ['--unknown-clients', 'always-allow'], { | |
| env: { ...process.env, FORCE_COLOR: '0' }, | |
| stdio: ['ignore', 'pipe', 'pipe'], | |
| }); | |
| let procOutput = ''; | |
| const handleData = (chunk) => { | |
| const text = stripAnsi(chunk.toString()); | |
| procOutput += text; | |
| outputBuffer += text; | |
| broadcast({ type: 'output', text }); | |
| }; | |
| shellularProc.stdout.on('data', handleData); | |
| shellularProc.stderr.on('data', handleData); | |
| shellularProc.on('error', (err) => { | |
| const text = `\n[spawn error] ${err.message}\n`; | |
| outputBuffer += text; | |
| broadcast({ type: 'output', text }); | |
| shellularProc = null; | |
| broadcast({ type: 'status', status: 'error' }); | |
| }); | |
| shellularProc.on('exit', (code, signal) => { | |
| shellularProc = null; | |
| const isRegError = code === 1 && !signal && | |
| (procOutput.includes('invalid_union') || procOutput.includes('Too many requests') || | |
| procOutput.includes('host registration')); | |
| if (isRegError) { | |
| const WAIT = 30; | |
| const msg = `\nβ Registration rate-limited by shellular API.\n` + | |
| ` Retrying automatically in ${WAIT}s β please waitβ¦\n`; | |
| outputBuffer += msg; | |
| broadcast({ type: 'output', text: msg }); | |
| broadcast({ type: 'status', status: 'retrying' }); | |
| retryTimer = setTimeout(() => { | |
| retryTimer = null; | |
| const msg2 = '\n[Retrying registrationβ¦]\n'; | |
| outputBuffer += msg2; | |
| broadcast({ type: 'output', text: msg2 }); | |
| startShellular(); | |
| }, WAIT * 1000); | |
| } else { | |
| const text = code !== 0 | |
| ? `\n[shellular exited β code=${code ?? '?'}, signal=${signal ?? 'none'}]\n` | |
| : '\n[shellular disconnected]\n'; | |
| outputBuffer += text; | |
| broadcast({ type: 'output', text }); | |
| broadcast({ type: 'status', status: 'stopped' }); | |
| } | |
| }); | |
| broadcast({ type: 'status', status: 'running' }); | |
| } | |
| function stopShellular() { | |
| if (retryTimer) { clearTimeout(retryTimer); retryTimer = null; } | |
| if (!shellularProc) return; | |
| shellularProc.kill('SIGTERM'); | |
| shellularProc = null; | |
| } | |
| // ββ Python sync.py subprocess βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| let syncProc = null; | |
| function startSyncPy() { | |
| if (syncProc) return; | |
| console.log('[sync] Starting sync.py...'); | |
| syncProc = spawn('python3', [path.join(__dirname, 'syn.py')], { | |
| env: { ...process.env }, | |
| stdio: ['ignore', 'pipe', 'pipe'], | |
| }); | |
| syncProc.stdout.on('data', (chunk) => { | |
| console.log('[sync]', chunk.toString().trim()); | |
| }); | |
| syncProc.stderr.on('data', (chunk) => { | |
| console.error('[sync:err]', chunk.toString().trim()); | |
| }); | |
| syncProc.on('error', (err) => { | |
| console.error('[sync] Spawn error:', err.message); | |
| syncProc = null; | |
| }); | |
| syncProc.on('exit', (code, signal) => { | |
| console.warn(`[sync] sync.py exited β code=${code ?? '?'}, signal=${signal ?? 'none'}`); | |
| syncProc = null; | |
| setTimeout(startSyncPy, 10_000); | |
| }); | |
| } | |
| function stopSyncPy() { | |
| if (!syncProc) return; | |
| syncProc.kill('SIGTERM'); | |
| syncProc = null; | |
| } | |
| // ββ SSH server setup ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function setupSSH() { | |
| try { | |
| // Set root password | |
| execSync(`echo "root:${SSH_PASSWORD}" | chpasswd`, { stdio: 'ignore' }); | |
| console.log('[ssh] root password set'); | |
| // Allow root + password login | |
| fs.mkdirSync('/etc/ssh/sshd_config.d', { recursive: true }); | |
| fs.writeFileSync('/etc/ssh/sshd_config.d/99-termius.conf', [ | |
| 'PermitRootLogin yes', | |
| 'PasswordAuthentication yes', | |
| 'ChallengeResponseAuthentication no', | |
| 'UsePAM no', | |
| 'PrintMotd no', | |
| ].join('\n') + '\n', 'utf-8'); | |
| // Generate host keys (no-op if already present) | |
| execSync('ssh-keygen -A', { stdio: 'ignore' }); | |
| // Launch sshd | |
| const sshd = spawn('/usr/sbin/sshd', ['-D', '-e'], { | |
| detached: true, | |
| stdio: 'ignore', | |
| }); | |
| sshd.unref(); | |
| console.log('[ssh] sshd started, pid:', sshd.pid); | |
| // Give sshd a moment then open the bore tunnel | |
| setTimeout(startBore, 2000); | |
| } catch (err) { | |
| console.error('[ssh] setup error:', err.message); | |
| } | |
| } | |
| // ββ Bore tunnel (exposes SSH port to the internet) ββββββββββββββββββββββββββββ | |
| let boreProc = null; | |
| let boreHost = null; | |
| let borePort = null; | |
| function startBore() { | |
| if (boreProc) return; | |
| console.log('[bore] starting tunnelβ¦'); | |
| boreProc = spawn('bore', ['local', '22', '--to', 'bore.pub'], { | |
| stdio: ['ignore', 'pipe', 'pipe'], | |
| }); | |
| const onData = (chunk) => { | |
| const text = chunk.toString(); | |
| console.log('[bore]', text.trim()); | |
| // bore prints: "β¦ Listening at bore.pub:NNNNN" | |
| const m = text.match(/bore\.pub:(\d+)/i); | |
| if (m) { | |
| boreHost = 'bore.pub'; | |
| borePort = parseInt(m[1], 10); | |
| console.log(`[bore] tunnel ready β ${boreHost}:${borePort}`); | |
| } | |
| }; | |
| boreProc.stdout.on('data', onData); | |
| boreProc.stderr.on('data', onData); | |
| boreProc.on('error', (err) => { | |
| console.error('[bore] error:', err.message); | |
| boreProc = null; boreHost = null; borePort = null; | |
| setTimeout(startBore, 15_000); | |
| }); | |
| boreProc.on('exit', (code, signal) => { | |
| console.warn(`[bore] exited code=${code} signal=${signal} β restarting in 10 s`); | |
| boreProc = null; boreHost = null; borePort = null; | |
| setTimeout(startBore, 10_000); | |
| }); | |
| } | |
| function stopBore() { | |
| if (!boreProc) return; | |
| boreProc.kill('SIGTERM'); | |
| boreProc = null; boreHost = null; borePort = null; | |
| } | |
| // ββ SSH info endpoint (used by frontend to show Termius credentials) ββββββββββ | |
| app.get('/api/ssh-info', requireAuth, (_req, res) => { | |
| res.json({ | |
| ready: !!(boreHost && borePort), | |
| host: boreHost, | |
| port: borePort, | |
| username: 'root', | |
| password: SSH_PASSWORD, | |
| }); | |
| }); | |
| // ββ SSE stream βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| app.get('/api/stream', requireAuth, (req, res) => { | |
| res.setHeader('Content-Type', 'text/event-stream'); | |
| res.setHeader('Cache-Control', 'no-cache'); | |
| res.setHeader('Connection', 'keep-alive'); | |
| res.setHeader('X-Accel-Buffering', 'no'); | |
| res.flushHeaders(); | |
| send(res, { type: 'status', status: shellularProc ? 'running' : 'stopped' }); | |
| if (outputBuffer) { | |
| send(res, { type: 'output', text: outputBuffer }); | |
| } | |
| sseClients.add(res); | |
| req.on('close', () => sseClients.delete(res)); | |
| }); | |
| // ββ Control endpoints ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| app.post('/api/shellular/start', requireAuth, (_req, res) => { | |
| startShellular(); | |
| res.json({ ok: true, running: !!shellularProc }); | |
| }); | |
| app.post('/api/shellular/stop', requireAuth, (_req, res) => { | |
| stopShellular(); | |
| outputBuffer = ''; | |
| broadcast({ type: 'output', text: '' }); | |
| res.json({ ok: true }); | |
| }); | |
| app.post('/api/shellular/restart', requireAuth, (_req, res) => { | |
| stopShellular(); | |
| outputBuffer = ''; | |
| broadcast({ type: 'clear' }); | |
| setTimeout(startShellular, 600); | |
| res.json({ ok: true }); | |
| }); | |
| app.get('/api/status', requireAuth, (_req, res) => { | |
| res.json({ running: !!shellularProc }); | |
| }); | |
| app.get('/api/setup-status', requireAuth, (_req, res) => { | |
| const seeded = !!( | |
| process.env.SHELLULAR_HOST_ID && | |
| process.env.SHELLULAR_KEY && | |
| process.env.SHELLULAR_MACHINE_ID | |
| ); | |
| res.json({ seeded }); | |
| }); | |
| app.get('/api/shellular/credentials', requireAuth, (_req, res) => { | |
| try { | |
| const shellularDir = path.join(os.homedir(), '.shellular'); | |
| const configRaw = fs.readFileSync(path.join(shellularDir, 'config.json'), 'utf-8'); | |
| const { hostId, machineId } = JSON.parse(configRaw); | |
| const keyFile = path.join(shellularDir, `shellular-${machineId}.e2ee`); | |
| const keyB64 = fs.readFileSync(keyFile).toString('base64'); | |
| res.json({ hostId, machineId, keyB64 }); | |
| } catch { | |
| res.status(404).json({ error: 'Not registered yet.' }); | |
| } | |
| }); | |
| app.get('/api/shellular/qr-data', requireAuth, (_req, res) => { | |
| try { | |
| const shellularDir = path.join(os.homedir(), '.shellular'); | |
| const configRaw = fs.readFileSync(path.join(shellularDir, 'config.json'), 'utf-8'); | |
| const { hostId, machineId } = JSON.parse(configRaw); | |
| const keyFile = path.join(shellularDir, `shellular-${machineId}.e2ee`); | |
| const keyB64 = fs.readFileSync(keyFile).toString('base64'); | |
| res.json({ qrData: `${hostId}:${keyB64}` }); | |
| } catch { | |
| res.status(404).json({ error: 'Config not seeded yet.' }); | |
| } | |
| }); | |
| // ββ Start ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| app.listen(PORT, '0.0.0.0', () => { | |
| console.log(`Shellular Web UI β http://0.0.0.0:${PORT}`); | |
| startSyncPy(); // π Sync to HF dataset | |
| startShellular(); // π Shellular for QR access | |
| setupSSH(); // π SSH server + bore tunnel for Termius | |
| }); | |
| // ββ Graceful shutdown ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| process.on('SIGTERM', () => { stopShellular(); stopSyncPy(); stopBore(); }); | |
| process.on('SIGINT', () => { stopShellular(); stopSyncPy(); stopBore(); }); | |