Spaces:
Running
Running
| FROM python:3.11-slim | |
| # Install dependencies for installing other tools | |
| RUN apt-get update && apt-get install -y \ | |
| curl \ | |
| git \ | |
| ipython3 \ | |
| openssh-client \ | |
| vim \ | |
| unzip \ | |
| && rm -rf /var/lib/apt/lists/* | |
| # Install uv (specific version 0.9.17) | |
| RUN curl -LsSf https://astral.sh/uv/0.9.17/install.sh | sh | |
| ENV PATH="/root/.local/bin:$PATH" | |
| # Install nvm and Node.js v24 | |
| ENV NVM_DIR="/root/.nvm" | |
| RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash \ | |
| && . "$NVM_DIR/nvm.sh" \ | |
| && nvm install 24 \ | |
| && nvm alias default 24 \ | |
| && nvm use default | |
| # Add Node and npm to PATH | |
| ENV PATH="$NVM_DIR/versions/node/v24.12.0/bin:$PATH" | |
| # Enable pnpm | |
| RUN corepack enable pnpm | |
| # Shorten terminal hostname/prompt for interactive shells | |
| RUN printf '%s\n' \ | |
| 'export PROMPT_HOST_SHORT="${PROMPT_HOST_SHORT:-$(hostname | cut -c1-8)}"' \ | |
| 'export PS1="\\u@${PROMPT_HOST_SHORT}:\\w\\$ "' \ | |
| '' \ | |
| '# Codex auth: device flow works best in container/web terminals.' \ | |
| "alias codex-login='codex login --device-auth'" \ | |
| "printf '\\n[codex] Tip: in Spaces/web terminals use device auth: codex login --device-auth (or codex-login)\\n\\n'" \ | |
| >> /root/.bashrc | |
| # Install CLI tools (network required at build time) | |
| RUN npm i -g @openai/codex @google/gemini-cli \ | |
| && npm i -g @modelcontextprotocol/server-filesystem \ | |
| && curl -fsSL https://claude.ai/install.sh | bash \ | |
| && true | |
| # claude install.sh typically drops binaries in ~/.local/bin | |
| ENV PATH="/root/.local/bin:$PATH" | |
| # Working directory | |
| WORKDIR /app | |
| # Install Node dependencies needed by the app (Codex SDK) | |
| COPY package.json . | |
| RUN npm install --omit=dev | |
| # Copy python dependencies | |
| COPY requirements.txt . | |
| # Install python dependencies using uv | |
| RUN uv pip install --system --no-cache -r requirements.txt | |
| # Copy application | |
| COPY . . | |
| # Bundle Supabase JS locally so Spaces can run without external CDNs. | |
| RUN node - <<'NODE' | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| function walk(dir, out = []) { | |
| for (const ent of fs.readdirSync(dir, { withFileTypes: true })) { | |
| const p = path.join(dir, ent.name); | |
| if (ent.isDirectory()) walk(p, out); | |
| else out.push(p); | |
| } | |
| return out; | |
| } | |
| const base = path.join(process.cwd(), 'node_modules', '@supabase', 'supabase-js'); | |
| if (!fs.existsSync(base)) { | |
| console.log('[build] @supabase/supabase-js not installed; skipping bundle copy'); | |
| process.exit(0); | |
| } | |
| const dist = path.join(base, 'dist'); | |
| const files = fs.existsSync(dist) ? walk(dist) : walk(base); | |
| const candidates = files.filter((f) => /supabase(\.min)?\.js$/i.test(f)).sort(); | |
| if (!candidates.length) { | |
| console.log('[build] No supabase JS bundle found in package; skipping'); | |
| process.exit(0); | |
| } | |
| const src = | |
| candidates.find((f) => /umd[\\/].*supabase\.min\.js$/i.test(f)) || | |
| candidates.find((f) => /umd[\\/].*supabase\.js$/i.test(f)) || | |
| candidates.find((f) => /supabase\.min\.js$/i.test(f)) || | |
| candidates[0]; | |
| const dst = path.join(process.cwd(), 'static', 'vendor', 'supabase-js.min.js'); | |
| fs.mkdirSync(path.dirname(dst), { recursive: true }); | |
| fs.copyFileSync(src, dst); | |
| console.log(`[build] Copied ${src} -> ${dst}`); | |
| NODE | |
| # Expose port 7860 for HF Spaces | |
| EXPOSE 7860 | |
| # Codex auth: tokens should come from HF Spaces Secrets at runtime. | |
| # Leave CODEX_ACCOUNT_ID empty by default; set it as a Secret if your tokens require it. | |
| ENV CODEX_ACCOUNT_ID="" | |
| # Generate SSH keys at runtime (for git over SSH), then start app | |
| RUN chmod +x /app/docker-entrypoint.sh | |
| ENTRYPOINT ["/app/docker-entrypoint.sh"] | |
| CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"] | |