From 7d31f514e3067155832126eda0424e7eb9e35a2b Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Tue, 29 Sep 2026 09:16:58 +0000 Subject: [PATCH] fix: container sets up its own data directory (closes #75) bin/entrypoint.sh, still running as root, now creates DATA_DIR if missing and gives it and /data to the netwatch user with mode 750 before starting the backend as that user. An empty host directory owned by root, or one holding files from another uid, works with no step on the host, so the README no longer tells the operator to create or chown it. The image no longer sets that ownership at build time. Model: opus-5-5 --- Dockerfile | 3 ++- README.md | 15 +++------------ TODO.md | 7 +++++++ backend/README.md | 3 ++- bin/entrypoint.sh | 10 ++++++++++ 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index 417fc97..d13e53e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -77,8 +77,9 @@ COPY --from=frontend /app/dist /usr/share/nginx/html COPY --from=builder /src/netwatch-server /usr/local/bin/netwatch-server COPY bin/entrypoint.sh /usr/local/bin/entrypoint.sh +# bin/entrypoint.sh creates DATA_DIR at start and gives it and /data to +# the netwatch user, whatever is mounted there. ENV DATA_DIR=/data/reports -RUN mkdir -p /data/reports && chown -R netwatch:netwatch /data VOLUME /data # The default public port; PORT changes it. diff --git a/README.md b/README.md index 415457e..c42f007 100644 --- a/README.md +++ b/README.md @@ -191,8 +191,9 @@ only inside the container, on `127.0.0.1:8081`. The image: - Sends the security headers `REPO_POLICIES.md` requires on every response, as `security-headers.conf` sets them, in place of the backend's own - Stores reports in `DATA_DIR`, `/data/reports` by default, on the `/data` - volume. The backend runs as user `netwatch` (uid 1000), so a directory - bind-mounted at `/data` must be writable by uid 1000 + volume. Before the backend starts, the image creates `DATA_DIR` and gives it + and `/data` to user `netwatch` (uid 1000), which the backend runs as, so a + host directory bind-mounted at `/data` ends up owned by uid 1000 - Writes buffered reports to disk on `docker stop`, and exits non-zero if nginx or the backend exits on its own, so the platform restarts it @@ -202,16 +203,6 @@ What the [upaas](https://git.eeqj.de/sneak/upaas) app for netwatch needs: - **Port:** container port `8080`. - **Volume:** container path `/data`; the reports are kept in `/data/reports`. -- **First run:** upaas bind-mounts the host directory it is given and does not - create it, and the backend, which runs as uid 1000, does not start unless it - can write there. Create the directory, owned by uid 1000, before the first - deploy: - - ```bash - mkdir -p /path/to/data - chown 1000:1000 /path/to/data - ``` - - **Environment variables:** none is required. An empty one counts as unset, and one set to a value netwatch cannot use stops the container at start, with the reason in its log. diff --git a/TODO.md b/TODO.md index 212faab..3eb53de 100644 --- a/TODO.md +++ b/TODO.md @@ -23,6 +23,13 @@ latest run passes. # Completed Steps +- 2026-09-29: the container sets up its own data directory (issue #75): + `bin/entrypoint.sh`, still as root, creates `DATA_DIR` if missing and gives it + and `/data` to the `netwatch` user with mode 750 before starting the backend + as that user, so an empty host directory owned by root, or one holding files + from another uid, works with no step on the host. The `README.md` first-run + step that created and chowned the host directory is gone, and the image no + longer sets that ownership at build time - 2026-09-29: `backend/.golangci.yml` re-vendored from `sneak/prompts` (issue #41): `gomodguard`, deprecated in golangci-lint v2.12.0, is disabled and its successor `gomodguard_v2` enabled with the org block list, so lint runs print diff --git a/backend/README.md b/backend/README.md index ad29c08..9914eb0 100644 --- a/backend/README.md +++ b/backend/README.md @@ -104,7 +104,8 @@ this server. The image's entrypoint, `bin/entrypoint.sh`, starts the server as user `netwatch` (uid 1000) with `BIND_ADDRESS=127.0.0.1` and `PORT=8081`, so only nginx reaches it, and with `TRUSTED_PROXIES=127.0.0.1/32`, so it takes the client address nginx passes on and no other. `DATA_DIR` is `/data/reports`, on -the `/data` volume, which `netwatch` owns. nginx replaces the security headers +the `/data` volume; the entrypoint creates it and gives it and `/data` to +`netwatch` before starting the server. nginx replaces the security headers this server sets with those in the root `security-headers.conf`, so those are what clients of the image see. diff --git a/bin/entrypoint.sh b/bin/entrypoint.sh index 4458f9e..ebde676 100755 --- a/bin/entrypoint.sh +++ b/bin/entrypoint.sh @@ -61,6 +61,16 @@ for proxy in $(printf '%s' "$TRUSTED_PROXIES" | tr ',' ' '); do echo "set_real_ip_from $cidr;" done > /etc/nginx/trusted-proxies.conf +# netwatch-server keeps its report files in DATA_DIR, on the /data +# volume, which may be a host directory owned by root or by another +# uid. Both are given to the netwatch user here, with the mode the +# server gives a directory it creates, so the host directory needs no +# preparing. +export DATA_DIR="${DATA_DIR:-/data/reports}" +mkdir -p "$DATA_DIR" || exit 1 +chown -R netwatch:netwatch /data "$DATA_DIR" || exit 1 +chmod 750 /data "$DATA_DIR" || exit 1 + # A stop signal is only noted here; the loop below acts on it. stop_requested="" trap 'stop_requested=yes' TERM INT