Spaces:
Paused
Paused
| 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"] |