diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b3881d5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +node_modules +.next +.git +data +*.db +*.db-* +.env +.env.* +!.env.example +coverage +tests +.github +.cursor +**/*.md +!README.md diff --git a/.env.example b/.env.example index 97401f5..8f6149a 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,14 @@ AUTH_SECRET=change-me-to-a-long-random-string # Optional: path to SQLite file (default ./data/rfid.db) # RFID_DB_PATH=./data/rfid.db +# Docker Compose published port (default 3000) +# RFID_PORT=3000 + +# Optional first-boot user when using Docker entrypoint +# CREATE_USER_EMAIL=admin@lab.local +# CREATE_USER_PASSWORD=changeme +# CREATE_USER_NAME=Admin + # Optional OIDC (Authentik, Keycloak, Authelia, etc.) # AUTH_OIDC_ISSUER=https://sso.example.com/application/o/rfid/ # AUTH_OIDC_CLIENT_ID= diff --git a/Dockerfile b/Dockerfile index 62ce9bb..b10b00c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,18 +2,21 @@ FROM node:22-bookworm-slim AS deps WORKDIR /app -RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y --no-install-recommends python3 make g++ \ + && rm -rf /var/lib/apt/lists/* COPY package.json package-lock.json ./ RUN npm ci FROM node:22-bookworm-slim AS builder WORKDIR /app -RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y --no-install-recommends python3 make g++ \ + && rm -rf /var/lib/apt/lists/* COPY --from=deps /app/node_modules ./node_modules COPY . . ENV NEXT_TELEMETRY_DISABLED=1 ENV AUTH_SECRET=build-time-placeholder-secret-32chars -RUN npm run build +RUN npm run build \ + && npm prune --omit=dev FROM node:22-bookworm-slim AS runner WORKDIR /app @@ -21,16 +24,32 @@ ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 ENV RFID_DB_PATH=/data/rfid.db ENV PORT=3000 -RUN apt-get update && apt-get install -y libstdc++6 && rm -rf /var/lib/apt/lists/* -RUN mkdir -p /data && chown node:node /data -COPY --from=builder /app/package.json /app/package-lock.json ./ -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/.next ./.next -COPY --from=builder /app/public ./public -COPY --from=builder /app/scripts ./scripts -COPY --from=builder /app/src ./src -COPY --from=builder /app/tsconfig.json ./tsconfig.json +ENV HOSTNAME=0.0.0.0 +ENV AUTH_TRUST_HOST=true + +RUN apt-get update && apt-get install -y --no-install-recommends libstdc++6 curl \ + && rm -rf /var/lib/apt/lists/* \ + && mkdir -p /data \ + && chown node:node /data + +COPY --from=builder --chown=node:node /app/package.json /app/package-lock.json ./ +COPY --from=builder --chown=node:node /app/node_modules ./node_modules +COPY --from=builder --chown=node:node /app/.next ./.next +COPY --from=builder --chown=node:node /app/public ./public +COPY --from=builder --chown=node:node /app/scripts ./scripts +COPY --from=builder --chown=node:node /app/src ./src +COPY --from=builder --chown=node:node /app/tsconfig.json ./tsconfig.json +# tsx is needed for create-user / entrypoint bootstrap (devDependency pruned above) +RUN npm install --omit=dev tsx@4.20.5 \ + && chown -R node:node /app/node_modules +COPY --chown=node:node docker/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + USER node EXPOSE 3000 VOLUME ["/data"] +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ + CMD curl -fsS http://127.0.0.1:3000/login >/dev/null || exit 1 + +ENTRYPOINT ["/entrypoint.sh"] CMD ["npm", "run", "start"] diff --git a/README.md b/README.md index 80e29a1..8f7b55c 100644 --- a/README.md +++ b/README.md @@ -86,16 +86,46 @@ GitHub Actions runs lint, typecheck, and tests on every pull request and push to ## Docker +### docker compose (recommended) + +```bash +cp .env.example .env +# set AUTH_SECRET to a long random string + +# optional first user on boot: +# echo 'CREATE_USER_EMAIL=admin@lab.local' >> .env +# echo 'CREATE_USER_PASSWORD=changeme' >> .env + +docker compose up -d --build +``` + +App: http://localhost:3000 — SQLite persists in the `rfid-data` volume. + +Create a user later (if you skipped bootstrap): + +```bash +docker compose exec rfid-database \ + npx tsx scripts/create-user.ts --email you@lab.local --password 'secret' --name You +``` + +Stop / remove (keeps volume): + +```bash +docker compose down +``` + +### Plain docker + ```bash docker build -t rfid-database . docker run --rm -p 3000:3000 \ -e AUTH_SECRET=your-long-secret \ + -e CREATE_USER_EMAIL=admin@lab.local \ + -e CREATE_USER_PASSWORD=changeme \ -v rfid-data:/data \ rfid-database ``` -Create the first user against the mounted DB (exec into the container or run `create-user` with `RFID_DB_PATH` pointed at the volume). - ## Security Tag dumps often include sector keys. Keep the app behind HTTPS, do not expose it publicly without auth, and treat `data/rfid.db` as sensitive. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..abed71d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,38 @@ +services: + rfid-database: + build: + context: . + dockerfile: Dockerfile + image: rfid-database:local + container_name: rfid-database + restart: unless-stopped + ports: + - "${RFID_PORT:-3000}:3000" + env_file: + - .env + environment: + NODE_ENV: production + RFID_DB_PATH: /data/rfid.db + PORT: "3000" + HOSTNAME: 0.0.0.0 + AUTH_TRUST_HOST: "true" + # Optional bootstrap (only creates if missing): + # CREATE_USER_EMAIL: admin@lab.local + # CREATE_USER_PASSWORD: change-me + # CREATE_USER_NAME: Admin + # Optional OIDC — also set NEXT_PUBLIC_* in .env if using SSO button + # AUTH_OIDC_ISSUER: ${AUTH_OIDC_ISSUER:-} + # AUTH_OIDC_CLIENT_ID: ${AUTH_OIDC_CLIENT_ID:-} + # AUTH_OIDC_CLIENT_SECRET: ${AUTH_OIDC_CLIENT_SECRET:-} + # AUTH_OIDC_NAME: ${AUTH_OIDC_NAME:-SSO} + volumes: + - rfid-data:/data + healthcheck: + test: ["CMD", "curl", "-fsS", "http://127.0.0.1:3000/login"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 25s + +volumes: + rfid-data: diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 0000000..fdfcd37 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,21 @@ +#!/bin/sh +set -e + +if [ -z "$AUTH_SECRET" ] || [ "$AUTH_SECRET" = "change-me-to-a-long-random-string" ]; then + echo "ERROR: AUTH_SECRET must be set to a long random value." >&2 + exit 1 +fi + +mkdir -p "$(dirname "${RFID_DB_PATH:-/data/rfid.db}")" + +# Optional first-boot user: set CREATE_USER_EMAIL + CREATE_USER_PASSWORD +if [ -n "$CREATE_USER_EMAIL" ] && [ -n "$CREATE_USER_PASSWORD" ]; then + echo "Ensuring user $CREATE_USER_EMAIL exists (ignored if already present)..." + npx tsx scripts/create-user.ts \ + --email "$CREATE_USER_EMAIL" \ + --password "$CREATE_USER_PASSWORD" \ + --name "${CREATE_USER_NAME:-Admin}" \ + || true +fi + +exec "$@"