akenomainx4 commited on
Commit
4d86073
Β·
verified Β·
1 Parent(s): 8a294ef

fix: pin machine-id + seed shellular config from env vars to bypass rate limit

Browse files
Files changed (2) hide show
  1. Dockerfile +14 -14
  2. app.js +39 -6
Dockerfile CHANGED
@@ -1,24 +1,25 @@
1
  # ── Base image ────────────────────────────────────────────────────────────────
2
- # node:20-slim is Debian-based β€” needed for node-pty native compilation.
3
- # It ships with a built-in "node" user at UID 1000, which is what HF Spaces uses.
4
  FROM node:20-slim
5
 
6
- # ── System dependencies ───────────────────────────────────────────────────────
7
- # python3, make, g++ are required to build node-pty (native addon used by shellular)
8
  RUN apt-get update && \
9
- apt-get install -y --no-install-recommends \
10
- python3 \
11
- make \
12
- g++ \
13
- && rm -rf /var/lib/apt/lists/*
14
-
15
- # ── Use the existing "node" user (UID 1000) ───────────────────────────────────
16
- # node:20-slim already has a "node" user at UID 1000 β€” no need to create one.
 
 
 
 
17
  USER node
18
  ENV HOME=/home/node \
19
  PATH="/home/node/.npm-global/bin:${PATH}"
20
 
21
- # ── Install shellular globally (into the node user's prefix) ─────────────────
22
  RUN npm config set prefix /home/node/.npm-global && \
23
  npm install -g shellular
24
 
@@ -31,7 +32,6 @@ RUN npm install --omit=dev
31
  COPY --chown=node:node . .
32
 
33
  # ── Runtime ───────────────────────────────────────────────────────────────────
34
- # Hugging Face Spaces expects port 7860
35
  EXPOSE 7860
36
  ENV PORT=7860
37
 
 
1
  # ── Base image ────────────────────────────────────────────────────────────────
 
 
2
  FROM node:20-slim
3
 
4
+ # ── System dependencies (needed to compile node-pty used by shellular) ────────
 
5
  RUN apt-get update && \
6
+ apt-get install -y --no-install-recommends python3 make g++ && \
7
+ rm -rf /var/lib/apt/lists/*
8
+
9
+ # ── Pin a fixed machine-id ────────────────────────────────────────────────────
10
+ # node-machine-id reads /etc/machine-id. By writing a fixed value here the
11
+ # shellular config seeded from SHELLULAR_HOST_ID / SHELLULAR_KEY env vars will
12
+ # always match β€” no re-registration needed across container restarts.
13
+ RUN echo "d8904b4d338adf83688caac869f64c0b" > /etc/machine-id && \
14
+ mkdir -p /var/lib/dbus && \
15
+ echo "d8904b4d338adf83688caac869f64c0b" > /var/lib/dbus/machine-id
16
+
17
+ # ── Use the built-in "node" user (UID 1000, matches HF Spaces runtime) ────────
18
  USER node
19
  ENV HOME=/home/node \
20
  PATH="/home/node/.npm-global/bin:${PATH}"
21
 
22
+ # ── Install shellular globally ────────────────────────────────────────────────
23
  RUN npm config set prefix /home/node/.npm-global && \
24
  npm install -g shellular
25
 
 
32
  COPY --chown=node:node . .
33
 
34
  # ── Runtime ───────────────────────────────────────────────────────────────────
 
35
  EXPOSE 7860
36
  ENV PORT=7860
37
 
app.js CHANGED
@@ -1,6 +1,8 @@
1
  import express from 'express';
2
  import { spawn } from 'child_process';
3
  import crypto from 'crypto';
 
 
4
  import path from 'path';
5
  import { fileURLToPath } from 'url';
6
 
@@ -27,6 +29,42 @@ function stripAnsi(str) {
27
  return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><~]/g, '');
28
  }
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  // ── Auth routes ───────────────────────────────────────────────────────────────
31
  app.post('/api/login', (req, res) => {
32
  const { key } = req.body;
@@ -74,9 +112,6 @@ function startShellular() {
74
 
75
  broadcast({ type: 'status', status: 'starting' });
76
 
77
- // shellular registers the VPS with the relay and prints a QR code on first
78
- // connection. --unknown-clients always-allow means any phone that scans the
79
- // QR is let in without an extra interactive prompt.
80
  shellularProc = spawn('shellular', ['--unknown-clients', 'always-allow'], {
81
  env: { ...process.env, FORCE_COLOR: '0' },
82
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -124,10 +159,8 @@ app.get('/api/stream', requireAuth, (req, res) => {
124
  res.setHeader('X-Accel-Buffering', 'no'); // disable nginx buffering on HF
125
  res.flushHeaders();
126
 
127
- // Send current state immediately so the UI can paint the right view
128
  send(res, { type: 'status', status: shellularProc ? 'running' : 'stopped' });
129
 
130
- // Replay buffered output so a reconnecting client sees the existing QR code
131
  if (outputBuffer) {
132
  send(res, { type: 'output', text: outputBuffer });
133
  }
@@ -145,7 +178,7 @@ app.post('/api/shellular/start', requireAuth, (_req, res) => {
145
  app.post('/api/shellular/stop', requireAuth, (_req, res) => {
146
  stopShellular();
147
  outputBuffer = '';
148
- broadcast({ type: 'output', text: '' }); // clear clients
149
  res.json({ ok: true });
150
  });
151
 
 
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';
6
  import path from 'path';
7
  import { fileURLToPath } from 'url';
8
 
 
29
  return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><~]/g, '');
30
  }
31
 
32
+ // ── Pre-seed shellular config from env vars ───────────────────────────────────
33
+ // If SHELLULAR_HOST_ID and SHELLULAR_KEY are set (as HF Secrets), we write
34
+ // them into ~/.shellular/ so shellular skips the registration API call entirely.
35
+ // This avoids rate-limit errors during container cold-starts.
36
+ function seedShellularConfig() {
37
+ const hostId = process.env.SHELLULAR_HOST_ID;
38
+ const keyB64 = process.env.SHELLULAR_KEY; // base64-encoded 32-byte key
39
+ const machineId = process.env.SHELLULAR_MACHINE_ID; // must match registration
40
+
41
+ if (!hostId || !keyB64 || !machineId) return;
42
+
43
+ const shellularDir = path.join(os.homedir(), '.shellular');
44
+ const configFile = path.join(shellularDir, 'config.json');
45
+ const keyFile = path.join(shellularDir, `shellular-${machineId}.e2ee`);
46
+
47
+ try {
48
+ fs.mkdirSync(shellularDir, { recursive: true });
49
+
50
+ // Write config.json (skips registration on next shellular start)
51
+ if (!fs.existsSync(configFile)) {
52
+ fs.writeFileSync(configFile, JSON.stringify({ hostId, machineId }), 'utf-8');
53
+ console.log(`[shellular] seeded config: hostId=${hostId}`);
54
+ }
55
+
56
+ // Write the E2E key file (32 bytes from base64)
57
+ if (!fs.existsSync(keyFile)) {
58
+ fs.writeFileSync(keyFile, Buffer.from(keyB64, 'base64'), { mode: 0o600 });
59
+ console.log(`[shellular] seeded key: ${keyFile}`);
60
+ }
61
+ } catch (err) {
62
+ console.error('[shellular] failed to seed config:', err.message);
63
+ }
64
+ }
65
+
66
+ seedShellularConfig();
67
+
68
  // ── Auth routes ───────────────────────────────────────────────────────────────
69
  app.post('/api/login', (req, res) => {
70
  const { key } = req.body;
 
112
 
113
  broadcast({ type: 'status', status: 'starting' });
114
 
 
 
 
115
  shellularProc = spawn('shellular', ['--unknown-clients', 'always-allow'], {
116
  env: { ...process.env, FORCE_COLOR: '0' },
117
  stdio: ['ignore', 'pipe', 'pipe'],
 
159
  res.setHeader('X-Accel-Buffering', 'no'); // disable nginx buffering on HF
160
  res.flushHeaders();
161
 
 
162
  send(res, { type: 'status', status: shellularProc ? 'running' : 'stopped' });
163
 
 
164
  if (outputBuffer) {
165
  send(res, { type: 'output', text: outputBuffer });
166
  }
 
178
  app.post('/api/shellular/stop', requireAuth, (_req, res) => {
179
  stopShellular();
180
  outputBuffer = '';
181
+ broadcast({ type: 'output', text: '' });
182
  res.json({ ok: true });
183
  });
184