diff --git a/Dockerfile b/Dockerfile index 2f751e4..b4c38c4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ RUN npm install --omit=dev FROM node:22-bookworm-slim ENV NODE_ENV=production RUN apt-get update \ - && apt-get install -y --no-install-recommends openssl ca-certificates tini \ + && apt-get install -y --no-install-recommends openssl ca-certificates tini util-linux \ && rm -rf /var/lib/apt/lists/* WORKDIR /app @@ -20,14 +20,19 @@ COPY package.json ./ COPY src ./src COPY public ./public COPY scripts ./scripts +COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh -RUN mkdir -p /data/photos && chown -R node:node /data /app -USER node +RUN chmod +x /usr/local/bin/docker-entrypoint.sh \ + && mkdir -p /data/photos /data/certs \ + && chown -R node:node /data /app + +# Starts as root only long enough to fix ownership of a bind-mounted /data, +# then the entrypoint drops to the node user before running anything. VOLUME ["/data"] EXPOSE 3000 3001 HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ CMD node scripts/healthcheck.mjs -ENTRYPOINT ["/usr/bin/tini", "--"] +ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/docker-entrypoint.sh"] CMD ["node", "src/server.js"] diff --git a/README.md b/README.md index 2238eee..562e44a 100644 --- a/README.md +++ b/README.md @@ -319,6 +319,28 @@ To back up: `docker compose stop && tar czf visitor-backup-$(date +%F).tar.gz da The kiosk returns to the home screen after two minutes of inactivity so the next visitor never sees the last one's details. +## Troubleshooting + +**`pull access denied for visitor-signin`** — something ran `docker compose pull`. The image is +built here, not fetched from a registry. Use `docker compose up -d --build`. The compose file +sets `pull_policy: build` so this should not come back. + +**`EACCES: permission denied, mkdir '/data/photos'`** — the bind-mounted `./data` on the host is +owned by root, and the app runs as an unprivileged user. The container's entrypoint fixes this +itself on start; if you are on an older build, do it by hand: + +```bash +sudo chown -R 1000:1000 data secrets +docker compose up -d --build +``` + +**Changes to the code do nothing** — Compose reuses the existing image. Always +`docker compose up -d --build` after a `git pull`. + +**Browser still warns about the certificate** — the authority is installed but not trusted. On +iOS that is a second, separate step under Settings → General → About → Certificate Trust +Settings. On Android, use a hostname rather than a bare IP. + ## Running without Docker ```bash diff --git a/docker-compose.yml b/docker-compose.yml index 90957f2..12f4a35 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,6 +2,9 @@ services: visitor-signin: build: . image: visitor-signin:latest + # Built from this folder, never fetched from a registry. Without this, + # `docker compose pull` fails trying to find it on Docker Hub. + pull_policy: build container_name: visitor-signin restart: unless-stopped env_file: diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..0b65973 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# A bind-mounted ./data is created on the host as root, and the chown in the +# Dockerfile only applies to the image layer that the mount then hides. So fix +# ownership here, at runtime, before dropping to the unprivileged user. +set -e + +DATA_DIR="${DATA_DIR:-/data}" + +if [ "$(id -u)" = "0" ]; then + mkdir -p "$DATA_DIR/photos" "$DATA_DIR/certs" + + # Only touch ownership when it is actually wrong, so a large photo archive + # is not walked on every restart. + if [ "$(stat -c %u "$DATA_DIR")" != "$(id -u node)" ]; then + echo "[entrypoint] taking ownership of $DATA_DIR for the node user" + chown -R node:node "$DATA_DIR" + fi + + if command -v setpriv >/dev/null 2>&1; then + exec setpriv --reuid=node --regid=node --init-groups "$@" + elif command -v runuser >/dev/null 2>&1; then + exec runuser -u node -- "$@" + else + echo "[entrypoint] no setpriv or runuser available, staying as root" >&2 + exec "$@" + fi +fi + +# Already running as a non-root user, because compose set `user:`. Nothing to fix +# here: if the mount is not writable the app will say so plainly on start. +if [ ! -w "$DATA_DIR" ]; then + echo "[entrypoint] $DATA_DIR is not writable by $(id -un) (uid $(id -u))." >&2 + echo "[entrypoint] On the docker host run: sudo chown -R $(id -u):$(id -g) ./data" >&2 + exit 1 +fi + +exec "$@" diff --git a/public/admin.html b/public/admin.html index 4ba3028..1dbdd37 100644 --- a/public/admin.html +++ b/public/admin.html @@ -9,67 +9,78 @@ -