Initial implementation of Bluetooth toy control app
CI / Static checks (push) Successful in 1m27s
CI / Build and push image (push) Skipped

Next.js app with a browser-side buttplug/buttplug-wasm control layer
(server never touches real-time device commands), SQLite storage via
Drizzle, single-secret auth, recordings/replay with device remapping,
a usage stats dashboard, Docker deployment, and Gitea CI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W8WkFF5ppURBAB918593Eb
This commit is contained in:
2026-08-25 07:51:38 +02:00
co-authored by Claude Sonnet 5
commit 1119c8eea0
112 changed files with 15522 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
# `output: 'standalone'` in next.config.ts (no custom server here, unlike
# pulsenode's, so standalone is viable) traces only what's actually required
# and copies better-sqlite3's compiled native binary along with it - verified
# by inspecting .next/standalone/node_modules after a local build.
FROM node:22-alpine AS base
RUN corepack enable
FROM base AS deps
WORKDIR /app
# better-sqlite3 ships prebuilt binaries for linux-musl (this image's libc),
# but the toolchain is installed defensively in case no prebuild matches this
# Node version - prebuild-install falls back to compiling from source then.
RUN apk add --no-cache python3 make g++
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
RUN pnpm install --frozen-lockfile
FROM base AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm build
FROM node:22-alpine AS runtime
RUN apk add --no-cache tini
WORKDIR /app
ENV NODE_ENV=production
# Docker auto-sets HOSTNAME to the container id; the standalone server.js
# binds to `process.env.HOSTNAME || '0.0.0.0'`, so left unset it would bind
# only to that container-id hostname's address instead of all interfaces,
# making the app unreachable via the published port.
ENV HOSTNAME=0.0.0.0
RUN addgroup -g 10001 -S app && adduser -u 10001 -S app -G app
# Standalone output ships its own minimal node_modules + server.js; static
# assets, public/, and the drizzle migrations aren't traced (they're read
# from disk at runtime, not imported), so they're copied in separately.
COPY --from=build /app/.next/standalone ./
COPY --from=build /app/.next/static ./.next/static
COPY --from=build /app/public ./public
COPY --from=build /app/drizzle ./drizzle
COPY --from=build /app/healthcheck.mjs ./healthcheck.mjs
RUN mkdir -p /app/data && chown -R app:app /app
USER app
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD ["node", "healthcheck.mjs"]
ENTRYPOINT ["tini", "--"]
CMD ["node", "server.js"]