Foundation for an E2E-encrypted activity list per winter-list-claude-code-prompt.md. Server (Bun + Hono): - bun:sqlite with WAL and the spec's schema (idempotent migration) - opaque server-stored sessions, httpOnly cookie - signup / challenge / login / logout / me / password / recovery-challenge / recovery-complete - activity CRUD with strict visibility rules: private uses ciphertext+nonce, semi never serializes owner_id, public attributes the owner - tag store with normalisation + autocomplete (semi/public only) Frontend (Svelte 5 + Vite): - libsodium-wrappers-sumo for client crypto (Argon2id + XChaCha20-Poly1305). SUMO is required because the standard build omits crypto_pwhash. - IndexedDB-backed private tag index (never leaves the browser) - in-memory DEK (no localStorage); page reload re-prompts for password - signup shows the recovery code once; tag input merges server + private sources with clear labelling - Bokmål UI Crypto module (shared/crypto.ts): - pure, runs in both Bun and the browser via a runtime-conditional loader that papers over libsodium-wrappers-sumo's broken ESM entry (createRequire on server, Vite alias in the browser) - DEK wrap/unwrap, AEAD payload encryption, recovery code generation with a visually-unambiguous alphabet Verification: - 22 crypto round-trip tests (wrap/unwrap, AEAD tamper rejection, password change preserves ciphertexts, recovery still works after rotation) - typecheck passes for server and frontend - Vite production build succeeds; libsodium SUMO chunk is ~315 KB gzipped Single-image Containerfile for podman: builds frontend in a builder stage, runs Bun in a slim runtime; one volume for the SQLite file; BUILD_DATE / GIT_REVISION baked into OCI labels and /etc/build-info. Known limitation deferred for this commit: the recovery endpoint has no server-side proof of the recovery code (anyone who knows an email can lock out the legitimate user, though they can't read any data). Closed in the next commit.
67 lines
2.3 KiB
Docker
67 lines
2.3 KiB
Docker
# syntax=docker/dockerfile:1
|
|
#
|
|
# Vinterliste — single-image container.
|
|
# Build stage compiles the frontend with Vite; runtime image runs Bun.
|
|
|
|
ARG BUN_VERSION=1.3
|
|
ARG BUILD_DATE
|
|
ARG GIT_REVISION
|
|
|
|
# ---- Builder ---------------------------------------------------------------
|
|
FROM docker.io/oven/bun:${BUN_VERSION} AS builder
|
|
WORKDIR /app
|
|
|
|
# Install dependencies first so they cache independently of source.
|
|
COPY package.json bun.lockb* ./
|
|
RUN bun install --frozen-lockfile || bun install
|
|
|
|
# Copy the rest of the source and build the frontend bundle.
|
|
COPY tsconfig.json ./
|
|
COPY shared ./shared
|
|
COPY server ./server
|
|
COPY frontend ./frontend
|
|
RUN bun run build:frontend
|
|
|
|
# ---- Runtime ---------------------------------------------------------------
|
|
FROM docker.io/oven/bun:${BUN_VERSION}-slim
|
|
WORKDIR /app
|
|
|
|
ARG BUILD_DATE
|
|
ARG GIT_REVISION
|
|
|
|
# OCI labels keep the image traceable even when tagged :latest.
|
|
LABEL org.opencontainers.image.title="vinterliste" \
|
|
org.opencontainers.image.description="End-to-end encrypted winter activity list" \
|
|
org.opencontainers.image.source="https://example.invalid/vinterliste" \
|
|
org.opencontainers.image.created="${BUILD_DATE}" \
|
|
org.opencontainers.image.revision="${GIT_REVISION}"
|
|
|
|
# Bake the same info into /etc/build-info so a running container can report it.
|
|
RUN printf 'build_date=%s\ngit_revision=%s\n' "${BUILD_DATE:-unknown}" "${GIT_REVISION:-unknown}" > /etc/build-info
|
|
|
|
# Copy production artefacts only. node_modules is reproducible from package.json,
|
|
# but we install fresh to keep the runtime image small and unambiguous.
|
|
COPY package.json bun.lockb* ./
|
|
RUN bun install --frozen-lockfile --production || bun install --production
|
|
|
|
COPY --from=builder /app/shared ./shared
|
|
COPY --from=builder /app/server ./server
|
|
COPY --from=builder /app/frontend/dist ./frontend/dist
|
|
|
|
# SQLite WAL files live in /app/data, which is the documented mount point.
|
|
RUN mkdir -p /app/data && chown -R bun:bun /app/data
|
|
VOLUME /app/data
|
|
|
|
ENV NODE_ENV=production \
|
|
PORT=3000 \
|
|
VINTERLISTE_DB=/app/data/vinterliste.db \
|
|
BUILD_DATE=${BUILD_DATE} \
|
|
GIT_REVISION=${GIT_REVISION}
|
|
|
|
EXPOSE 3000
|
|
USER bun
|
|
|
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
CMD bun -e "fetch('http://localhost:3000/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
|
|
|
CMD ["bun", "run", "server/index.ts"]
|