RFID tag dump PWA + REST API (with Docker Compose) (#2)

* Implement RFID tag dump PWA with REST API, auth, and CI

Add Next.js app with SQLite/Drizzle storage for sites and LF/HF tag
dumps, multi-user Auth.js (credentials + optional OIDC), personal API
tokens in Settings, versioned /api/v1 for UI and future CLI use,
MCT/Proxmark/JSON import-export, PWA offline shell, Vitest tests, and
GitHub Actions CI.

* Add Docker Compose setup with persistent volume

Improve the production Dockerfile (healthcheck, entrypoint bootstrap
user, pruned deps) and add docker-compose.yml plus .dockerignore for
one-command deploy with a named SQLite volume.

---------

Co-authored-by: Cursor Agent <[email protected]>
This commit is contained in:
Nick Trochalakis
2026-08-23 16:10:30 -07:00
committed by GitHub
co-authored by Cursor Agent
parent 8500d2f673
commit d3ed4566b7
6 changed files with 172 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
node_modules
.next
.git
data
*.db
*.db-*
.env
.env.*
!.env.example
coverage
tests
.github
.cursor
**/*.md
!README.md
+11
View File
@@ -2,6 +2,17 @@ AUTH_SECRET=change-me-to-a-long-random-string
# Optional: path to SQLite file (default ./data/rfid.db) # Optional: path to SQLite file (default ./data/rfid.db)
# RFID_DB_PATH=./data/rfid.db # RFID_DB_PATH=./data/rfid.db
<<<<<<< HEAD
# Docker Compose published port (default 3000)
# RFID_PORT=3000
# Optional first-boot user when using Docker entrypoint
# [email protected]
# CREATE_USER_PASSWORD=changeme
# CREATE_USER_NAME=Admin
=======
>>>>>>> origin/main
# Optional OIDC (Authentik, Keycloak, Authelia, etc.) # Optional OIDC (Authentik, Keycloak, Authelia, etc.)
# AUTH_OIDC_ISSUER=https://sso.example.com/application/o/rfid/ # AUTH_OIDC_ISSUER=https://sso.example.com/application/o/rfid/
# AUTH_OIDC_CLIENT_ID= # AUTH_OIDC_CLIENT_ID=
+46
View File
@@ -2,18 +2,33 @@
FROM node:22-bookworm-slim AS deps FROM node:22-bookworm-slim AS deps
WORKDIR /app WORKDIR /app
<<<<<<< HEAD
RUN apt-get update && apt-get install -y --no-install-recommends python3 make g++ \
&& rm -rf /var/lib/apt/lists/*
=======
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 python3 make g++ && rm -rf /var/lib/apt/lists/*
>>>>>>> origin/main
COPY package.json package-lock.json ./ COPY package.json package-lock.json ./
RUN npm ci RUN npm ci
FROM node:22-bookworm-slim AS builder FROM node:22-bookworm-slim AS builder
WORKDIR /app WORKDIR /app
<<<<<<< HEAD
RUN apt-get update && apt-get install -y --no-install-recommends python3 make g++ \
&& rm -rf /var/lib/apt/lists/*
=======
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 python3 make g++ && rm -rf /var/lib/apt/lists/*
>>>>>>> origin/main
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/node_modules ./node_modules
COPY . . COPY . .
ENV NEXT_TELEMETRY_DISABLED=1 ENV NEXT_TELEMETRY_DISABLED=1
ENV AUTH_SECRET=build-time-placeholder-secret-32chars ENV AUTH_SECRET=build-time-placeholder-secret-32chars
<<<<<<< HEAD
RUN npm run build \
&& npm prune --omit=dev
=======
RUN npm run build RUN npm run build
>>>>>>> origin/main
FROM node:22-bookworm-slim AS runner FROM node:22-bookworm-slim AS runner
WORKDIR /app WORKDIR /app
@@ -21,6 +36,36 @@ ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1 ENV NEXT_TELEMETRY_DISABLED=1
ENV RFID_DB_PATH=/data/rfid.db ENV RFID_DB_PATH=/data/rfid.db
ENV PORT=3000 ENV PORT=3000
<<<<<<< HEAD
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 [email protected] \
&& 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"]
=======
RUN apt-get update && apt-get install -y libstdc++6 && rm -rf /var/lib/apt/lists/* RUN apt-get update && apt-get install -y libstdc++6 && rm -rf /var/lib/apt/lists/*
RUN mkdir -p /data && chown node:node /data RUN mkdir -p /data && chown node:node /data
COPY --from=builder /app/package.json /app/package-lock.json ./ COPY --from=builder /app/package.json /app/package-lock.json ./
@@ -33,4 +78,5 @@ COPY --from=builder /app/tsconfig.json ./tsconfig.json
USER node USER node
EXPOSE 3000 EXPOSE 3000
VOLUME ["/data"] VOLUME ["/data"]
>>>>>>> origin/main
CMD ["npm", "run", "start"] CMD ["npm", "run", "start"]
+41
View File
@@ -86,16 +86,57 @@ GitHub Actions runs lint, typecheck, and tests on every pull request and push to
## Docker ## Docker
<<<<<<< HEAD
### docker compose (recommended)
```bash
cp .env.example .env
# set AUTH_SECRET to a long random string
# optional first user on boot:
# echo '[email protected]' >> .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 [email protected] --password 'secret' --name You
```
Stop / remove (keeps volume):
```bash
docker compose down
```
### Plain docker
=======
>>>>>>> origin/main
```bash ```bash
docker build -t rfid-database . docker build -t rfid-database .
docker run --rm -p 3000:3000 \ docker run --rm -p 3000:3000 \
-e AUTH_SECRET=your-long-secret \ -e AUTH_SECRET=your-long-secret \
<<<<<<< HEAD
-e CREATE_USER_EMAIL=[email protected] \
-e CREATE_USER_PASSWORD=changeme \
=======
>>>>>>> origin/main
-v rfid-data:/data \ -v rfid-data:/data \
rfid-database rfid-database
``` ```
<<<<<<< HEAD
=======
Create the first user against the mounted DB (exec into the container or run `create-user` with `RFID_DB_PATH` pointed at the volume). Create the first user against the mounted DB (exec into the container or run `create-user` with `RFID_DB_PATH` pointed at the volume).
>>>>>>> origin/main
## Security ## 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. 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.
+38
View File
@@ -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: [email protected]
# 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:
+21
View File
@@ -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 "$@"