# 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 . 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 = ''; if (html.indexOf('') !== -1) { html = html.replace('', script + ''); } 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"]