Spaces:
Sleeping
Sleeping
| # Open Design on Hugging Face Spaces | |
| # | |
| # To fork this with your own changes, replace the repo URL and ref below | |
| # with your own fork. | |
| ARG NODE_IMAGE=docker.io/library/node:24-slim | |
| FROM ${NODE_IMAGE} AS build | |
| RUN apt-get update && apt-get install -y git python3 make g++ && rm -rf /var/lib/apt/lists/* | |
| ARG CACHEBUST=7 | |
| WORKDIR /src | |
| RUN git clone --depth 1 --branch main \ | |
| https://github.com/LevelSUB-zero/Opencode.git . && \ | |
| echo "cloned at cachebust $CACHEBUST" | |
| RUN corepack enable && \ | |
| corepack prepare pnpm@10.33.2 --activate && \ | |
| pnpm install --frozen-lockfile | |
| RUN pnpm --filter @open-design/daemon build && \ | |
| pnpm --filter @open-design/web build && \ | |
| pnpm --filter @open-design/daemon deploy --legacy --prod /src/deploy/daemon && \ | |
| pnpm store prune && \ | |
| rm -rf /root/.cache /root/.local/share/pnpm/store | |
| FROM ${NODE_IMAGE} | |
| RUN apt-get update && apt-get install -y tini poppler-utils && rm -rf /var/lib/apt/lists/* && \ | |
| addgroup --system --gid 1001 open-design && \ | |
| adduser --system --no-create-home --shell /bin/sh --uid 1001 --ingroup open-design open-design | |
| # Install opencode-ai CLI as the primary agent runtime. | |
| RUN npm install -g opencode-ai@1.14.39 && \ | |
| opencode --version && \ | |
| chmod -R a+rX /usr/local/lib/node_modules/opencode-ai | |
| WORKDIR /app | |
| COPY --from=build --chown=open-design:open-design /src/deploy/daemon ./apps/daemon | |
| COPY --from=build --chown=open-design:open-design /src/apps/web/out ./apps/web/out | |
| COPY --from=build --chown=open-design:open-design /src/skills ./skills | |
| COPY --from=build --chown=open-design:open-design /src/design-systems ./design-systems | |
| COPY --from=build --chown=open-design:open-design /src/craft ./craft | |
| COPY --from=build --chown=open-design:open-design /src/prompt-templates ./prompt-templates | |
| COPY --from=build --chown=open-design:open-design /src/assets/frames ./assets/frames | |
| COPY --from=build --chown=open-design:open-design /src/assets/community-pets ./assets/community-pets | |
| COPY --from=build --chown=open-design:open-design /src/plugins/_official ./plugins/_official | |
| RUN mkdir -p /data/.od && chown -R open-design:open-design /data | |
| # Entrypoint: generate a runtime API token, inject it into the frontend so | |
| # API calls carry the Authorization header, fix /data volume permissions | |
| # (HF Spaces mounts the persistent volume at /data with root ownership), | |
| # then drop to the unprivileged open-design user and run the daemon. | |
| RUN cat > /entrypoint.sh << 'SCRIPT' | |
| #!/bin/sh | |
| set -e | |
| export OD_API_TOKEN=$(node -e "process.stdout.write(require('crypto').randomBytes(32).toString('hex'))") | |
| echo "OpenCode: $(opencode --version 2>&1)" | |
| # Prep tmpfs dirs for daemon DB and opencode data (avoid SQLite I/O on HF volume) | |
| rm -rf /tmp/od-data /tmp/opencode | |
| mkdir -p /tmp/opencode /tmp/od-data /data/.od | |
| # Write opencode-ai config and auth. The {env:VAR} template for apiKey | |
| # has a known bug (#19946), so we use direct shell expansion. | |
| if [ -n "$OPENCODE_API_KEY" ]; then | |
| mkdir -p /data/.config/opencode | |
| cat > /data/.config/opencode/opencode.json << OPCONFIG | |
| { | |
| "provider": { | |
| "opencodezen": { | |
| "options": { | |
| "apiKey": "$OPENCODE_API_KEY" | |
| } | |
| } | |
| } | |
| } | |
| OPCONFIG | |
| # auth.json lives under XDG_DATA_HOME/opencode/ (XDG_DATA_HOME=/tmp) | |
| cat > /tmp/opencode/auth.json << OPAUTH | |
| { | |
| "opencodezen": "$OPENCODE_API_KEY" | |
| } | |
| OPAUTH | |
| echo "OpenCode config + auth written (opencodezen)" | |
| fi | |
| # Determine the public origin URL for the isLocalSameOrigin bypass | |
| SPACE_SUBDOMAIN="${SPACE_SUBDOMAIN:-kratoszeus-design}" | |
| export OD_ALLOWED_ORIGINS="https://${SPACE_SUBDOMAIN}.hf.space" | |
| echo "Allowed origins: ${OD_ALLOWED_ORIGINS}" | |
| # Inject the API token into the frontend's index.html so all fetch calls | |
| # to /api/* automatically include Authorization: Bearer <token>. | |
| node -e " | |
| var fs = require('fs'); | |
| var token = process.env.OD_API_TOKEN; | |
| var html = fs.readFileSync('apps/web/out/index.html', 'utf8'); | |
| var script = '<script>window.__OD_API_TOKEN=' + JSON.stringify(token) + ';(function(){var f=window.fetch;window.fetch=function(u,o){o=o||{};var s=typeof u===\"string\"?u:u.toString();if(s.startsWith(\"/api/\")){o.headers=o.headers||{};o.headers[\"Authorization\"]=\"Bearer \"+window.__OD_API_TOKEN}return f.call(window,u,o)}})()</script>'; | |
| if (html.indexOf('</head>') !== -1) { | |
| html = html.replace('</head>', script + '</head>'); | |
| } else { | |
| html = script + html; | |
| } | |
| fs.writeFileSync('apps/web/out/index.html', html); | |
| " | |
| # Patch the compiled server.js at startup: | |
| # 1) openProbePaths uses req.originalUrl (req.path is relative to /api mount) | |
| # 2) Patch origin-validation.js so isLocalSameOrigin trusts same-origin requests | |
| node -e " | |
| var fs = require('fs'); | |
| var src = fs.readFileSync('apps/daemon/dist/server.js', 'utf8'); | |
| // 1) Fix openProbePaths: TSC splits 'if(...)return next()' across two lines | |
| src = src.replace( | |
| 'if (openProbePaths.has(req.path))\n return next();', | |
| 'if (openProbePaths.has(req.originalUrl))\n return next();' | |
| ); | |
| fs.writeFileSync('apps/daemon/dist/server.js', src); | |
| // 2) Patch origin-validation.js so all isLocalSameOrigin call sites trust same-origin | |
| var ovSrc = fs.readFileSync('apps/daemon/dist/origin-validation.js', 'utf8'); | |
| ovSrc = ovSrc.replace( | |
| 'export function isLocalSameOrigin(req, port, env = process.env) {', | |
| 'export function isLocalSameOrigin(req, port, env = process.env) { var _o=headerValue(req.headers?.origin);if(typeof _o===\"string\"&&_o.length>0){try{var u=new URL(_o);if(u.host===req.headers.host)return true}catch{}}var _r=headerValue(req.headers?.referer);if(typeof _r===\"string\"&&_r.length>0){try{var u=new URL(_r);if(u.host===req.headers.host)return true}catch{}}' | |
| ); | |
| fs.writeFileSync('apps/daemon/dist/origin-validation.js', ovSrc); | |
| console.log('Patched: openProbePaths + origin-validation'); | |
| " | |
| # Pass OPENCODE_API_KEY to the daemon via su -c (su drops most env vars). | |
| chown -R open-design:open-design /tmp/opencode /tmp/od-data /data | |
| exec su -s /bin/sh open-design -c "HOME=/data OPENCODE_API_KEY='$OPENCODE_API_KEY' XDG_DATA_HOME=/tmp OD_DATA_DIR=/tmp/od-data node apps/daemon/dist/cli.js --no-open" | |
| SCRIPT | |
| RUN chmod +x /entrypoint.sh | |
| ENV NODE_ENV=production | |
| ENV NODE_OPTIONS=--max-old-space-size=256 | |
| ENV OD_BIND_HOST=0.0.0.0 | |
| ENV OD_PORT=7860 | |
| ENV OD_DATA_DIR=/tmp/od-data | |
| # Shorter inactivity timeout so stuck opencode processes are cleaned up faster | |
| ENV OD_CHAT_RUN_INACTIVITY_TIMEOUT_MS=300000 | |
| # OPENCODE_API_KEY — set as a HF Space Secret for opencode-ai auth | |
| EXPOSE 7860 | |
| ENTRYPOINT ["/usr/bin/tini", "--", "/entrypoint.sh"] | |