FROM debian:bookworm-slim ARG VERSION=0.53.43 # ========================================================= # System dependencies (NO nodejs/npm here) # ========================================================= RUN apt-get update && apt-get install -y \ curl \ git \ python3 \ tar \ ca-certificates \ libssl-dev \ sudo \ netcat-openbsd \ xvfb \ xdotool \ libx11-6 \ libxext6 \ libxrandr2 \ libxfixes3 \ libxi6 \ libxrender1 \ libxss1 \ libxtst6 \ libgtk-3-0 \ libnss3 \ libasound2 \ libgbm1 \ libxkbcommon0 \ libxkbcommon-x11-0 \ && rm -rf /var/lib/apt/lists/* # ========================================================= # Install Node.js 24 (ONLY from NodeSource) # ========================================================= RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - && \ apt-get install -y nodejs && \ node --version && \ npm --version # ========================================================= # Install pnpm # ========================================================= RUN npm install -g pnpm@10.10.0 # ========================================================= # Install OpenHuman Core # ========================================================= RUN curl -L \ "https://github.com/tinyhumansai/openhuman/releases/download/v${VERSION}/openhuman-core-${VERSION}-x86_64-unknown-linux-gnu.tar.gz" \ -o core.tar.gz && \ mkdir -p /tmp/core && \ tar -xzf core.tar.gz -C /tmp/core && \ find /tmp/core -name openhuman-core -exec mv {} /usr/local/bin/openhuman-core \; && \ chmod +x /usr/local/bin/openhuman-core && \ rm -rf /tmp/core core.tar.gz # ========================================================= # Create app user # ========================================================= RUN useradd -m -u 1000 appuser USER appuser ENV HOME=/home/appuser WORKDIR /app # ========================================================= # Clone OpenHuman # ========================================================= RUN git clone --recursive \ --branch v${VERSION} \ https://github.com/tinyhumansai/openhuman.git /app # ========================================================= # Install frontend dependencies # ========================================================= RUN pnpm install # ========================================================= # Create RPC proxy (uses only built-in Node http module) # ========================================================= RUN cat > /app/rpc-proxy.js << 'EOF' const http = require('http'); const PROXY_PORT = 7789; const CORE_PORT = 7788; const CORE_TOKEN = "hf_space_token_123"; function forward(body) { return new Promise((resolve) => { const req = http.request({ hostname: '127.0.0.1', port: CORE_PORT, path: '/rpc', method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${CORE_TOKEN}` } }, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { try { resolve(JSON.parse(data)); } catch (e) { resolve({ jsonrpc: "2.0", error: { code: -32700, message: "Parse error" } }); } }); }); req.on('error', (e) => { resolve({ jsonrpc: "2.0", error: { code: -32000, message: String(e) } }); }); req.write(JSON.stringify(body)); req.end(); }); } const server = http.createServer((req, res) => { if (req.method !== 'POST' || req.url !== '/rpc') { res.writeHead(404); return res.end('Not found'); } let body = ''; req.on('data', chunk => body += chunk); req.on('end', async () => { let json; try { json = JSON.parse(body); } catch (e) { res.writeHead(400); return res.end('Bad JSON'); } const method = json.method || ''; console.log('RPC ->', method); // Force authenticated state if ( method === 'openhuman.app_state_snapshot' || method === 'openhuman.health_snapshot' ) { const original = await forward(json); if (!original.result) original.result = {}; original.result.authenticated = true; original.result.onboardingCompleted = true; original.result.sessionToken = "self-hosted"; original.result.coreMode = "runtime"; res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify(original)); } // Mock cloud RPCs if ( method.includes('billing') || method.includes('team') || method.includes('auth') || method.includes('profile') ) { res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ jsonrpc: "2.0", id: json.id, result: { mocked: true, selfHosted: true } })); } // Forward everything else const response = await forward(json); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(response)); }); }); server.listen(PROXY_PORT, '0.0.0.0', () => { console.log(`RPC Proxy running on port ${PROXY_PORT}`); }); EOF # ========================================================= # Create startup script # ========================================================= RUN cat > /app/start.sh << 'EOF' #!/bin/bash set -e export OPENHUMAN_CORE_TOKEN="hf_space_token_123" export OPENHUMAN_WORKSPACE="/home/appuser/workspace" mkdir -p "$OPENHUMAN_WORKSPACE" echo "=== STARTING XVFB ===" Xvfb :99 -screen 0 1024x768x24 -nolisten tcp & export DISPLAY=:99 sleep 2 echo "=== STARTING OPENHUMAN CORE ===" openhuman-core run \ --host 127.0.0.1 \ --port 7788 \ --jsonrpc-only \ > /tmp/core.log 2>&1 & echo "=== WAITING FOR CORE ===" for i in $(seq 1 60); do if nc -z 127.0.0.1 7788; then echo "=== CORE READY ===" break fi sleep 1 done echo "=== STARTING RPC PROXY ===" node /app/rpc-proxy.js & echo "=== WRITING VITE CONFIG ===" cat > /app/app/vite.hf.config.ts << 'VITECONFIG' import { defineConfig, mergeConfig } from "vite"; import baseConfig from "./vite.config"; export default defineConfig(async (env) => { const cfg = typeof baseConfig === "function" ? await baseConfig(env) : baseConfig; return mergeConfig(cfg, { server: { host: "0.0.0.0", port: 7860, allowedHosts: true, proxy: { "/rpc": { target: "http://127.0.0.1:7789", changeOrigin: true, ws: true } } } }); }); VITECONFIG echo "=== STARTING FRONTEND ===" cd /app/app tail -f /tmp/core.log & pnpm exec vite \ --config vite.hf.config.ts \ --host 0.0.0.0 \ --port 7860 EOF RUN chmod +x /app/start.sh EXPOSE 7860 ENV PORT=7860 CMD ["/app/start.sh"]