495b4aef36
The runtime crash on v0.2.3:
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/app/server/util.js'
imported from /app/server/index.js
happened because the Dockerfile's stage-2 COPY only listed server/
index.js + server/license.js explicitly. When I started extracting
modules in v0.2.3 (util.js, gemini-helpers.js, audio.js, ytdlp.js,
cookies.js, config.js, license-middleware.js, history.js, library.js)
I forgot to update the COPY list, so those files were never copied
into the runner image. Local 'node' tests passed because the modules
exist on disk; the .s9pk container had only the two original files
and crashed on first import.
Fix:
COPY server/*.js ./server/
Glob picks up all top-level .js files automatically, including any
future extractions, while still skipping server/test/ and server/
node_modules/. This is the simplest forward-compatible form.
Bonus: refresh the vendored @keysat/licensing-client from 0.1.0 to
0.2.0. The new SDK adds:
• policySlug field on StartPurchaseOptions (so we can drive Core/
Pro tier selection programmatically from our backend)
• client.listPublicPolicies(productSlug) for fetching the tier
cards' data without auth
Both are prerequisites for the in-app buy flow planned in
~/.claude/plans/in-app-buy-flow.md. The vendor's own node_modules
(@noble/ed25519, @noble/hashes) is gitignored as before — Docker
builds re-install via `npm install --omit=dev --ignore-scripts` in
the vendor dir during stage 1.
Also includes the license-middleware update from earlier in the day:
a 30s license-file poll so a key set via the "Set Recap License"
StartOS action is picked up within seconds (instead of waiting for
the 6h scheduled validateOnline tick).
78 lines
3.1 KiB
Docker
78 lines
3.1 KiB
Docker
# ─────────────────────────────────────────────────────────
|
|
# Recap — StartOS 0.4 Docker image
|
|
#
|
|
# Includes: Node.js 20, Python 3, yt-dlp, ffmpeg
|
|
#
|
|
# Uses Debian slim (not Alpine) because:
|
|
# - Debian is more reliable for pip-installed packages with C deps
|
|
# ─────────────────────────────────────────────────────────
|
|
|
|
# ── Stage 1: Install Node.js dependencies ──────────────────
|
|
FROM node:20-slim AS builder
|
|
|
|
# @keysat/licensing-client is a private git repo, so we vendor its built
|
|
# output into vendor/ and reference it via a file: dep. That removes any
|
|
# need for git or credentials in the build container.
|
|
#
|
|
# The vendor package needs its own node_modules (for @noble/ed25519 etc.)
|
|
# because npm's file: deps create a symlink, and Node's module resolver
|
|
# walking up from a symlinked location won't find the parent project's
|
|
# node_modules. Installing inside the vendor dir is the simplest fix.
|
|
WORKDIR /app
|
|
COPY vendor/keysat-licensing-client /app/vendor/keysat-licensing-client
|
|
WORKDIR /app/vendor/keysat-licensing-client
|
|
RUN npm install --omit=dev --ignore-scripts
|
|
WORKDIR /app/server
|
|
COPY server/package.json server/package-lock.json* ./
|
|
RUN npm ci --omit=dev --ignore-scripts 2>/dev/null || npm install --omit=dev --ignore-scripts
|
|
|
|
# ── Stage 2: Final runtime image ───────────────────────────
|
|
FROM node:20-slim AS runner
|
|
|
|
WORKDIR /app
|
|
|
|
# Install runtime dependencies:
|
|
# - dumb-init: proper PID 1 signal handling in containers
|
|
# - curl: health checks + yt-dlp binary downloads
|
|
# - python3 + pip: yt-dlp installation and updates
|
|
# - ffmpeg: audio extraction, splitting, and duration detection
|
|
# - ca-certificates: HTTPS for YouTube/Gemini API calls
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
dumb-init \
|
|
curl \
|
|
python3 \
|
|
python3-pip \
|
|
python3-venv \
|
|
ffmpeg \
|
|
ca-certificates \
|
|
&& rm -rf /var/lib/apt/lists/* \
|
|
&& pip3 install --break-system-packages yt-dlp \
|
|
&& yt-dlp --version
|
|
|
|
# Copy Node.js app from builder
|
|
COPY --from=builder /app/vendor ./vendor/
|
|
COPY --from=builder /app/server/node_modules ./server/node_modules/
|
|
COPY server/package.json ./server/
|
|
# Top-level *.js files only — picks up index.js, license.js, util.js,
|
|
# gemini-helpers.js, audio.js, ytdlp.js, cookies.js, config.js,
|
|
# license-middleware.js, history.js, library.js, and any future
|
|
# extractions automatically. (Glob skips subdirs like server/test/.)
|
|
COPY server/*.js ./server/
|
|
COPY public/ ./public/
|
|
COPY assets/ ./assets/
|
|
|
|
# Copy entrypoint scripts
|
|
COPY docker_entrypoint.sh /usr/local/bin/docker_entrypoint.sh
|
|
RUN chmod +x /usr/local/bin/docker_entrypoint.sh
|
|
|
|
# Create persistent data mount point
|
|
RUN mkdir -p /data
|
|
|
|
ENV NODE_ENV=production \
|
|
PORT=3001 \
|
|
DATA_DIR=/data
|
|
|
|
EXPOSE 3001
|
|
|
|
ENTRYPOINT ["dumb-init", "--", "/usr/local/bin/docker_entrypoint.sh"]
|