Compare commits

..

2 Commits

Author SHA1 Message Date
josh 29e6933505 Fix EACCES on bind-mounted /app/data
Build and Push / build (push) Successful in 54s
Bind mounts override the image's chown, so the container's nextjs
user (uid 1001) couldn't write to /app/data when it was mounted from
a host dir owned by someone else. Start as root, fix ownership in an
entrypoint, then drop to nextjs via su-exec.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 10:21:37 -04:00
josh ead2cdbc3c Document Docker deployment and refreshed chart UX in README
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 10:09:51 -04:00
3 changed files with 52 additions and 19 deletions
+8 -2
View File
@@ -24,7 +24,8 @@ ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000 ENV PORT=3000
ENV HOSTNAME=0.0.0.0 ENV HOSTNAME=0.0.0.0
RUN addgroup --system --gid 1001 nodejs \ RUN apk add --no-cache su-exec \
&& addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 nextjs && adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public COPY --from=builder /app/public ./public
@@ -33,7 +34,12 @@ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
RUN mkdir -p /app/data && chown nextjs:nodejs /app/data RUN mkdir -p /app/data && chown nextjs:nodejs /app/data
USER nextjs COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
# Stay as root so the entrypoint can fix bind-mount ownership, then drop
# privileges via su-exec before launching the server.
EXPOSE 3000 EXPOSE 3000
VOLUME ["/app/data"] VOLUME ["/app/data"]
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["node", "server.js"] CMD ["node", "server.js"]
+34 -17
View File
@@ -10,7 +10,7 @@ Built with Next.js 16, TypeScript, and Tailwind CSS.
- **Leaderboard** — per-user request count, total storage, average GB per request, and optional Tautulli watch stats (plays, watch hours), each ranked against the full userbase - **Leaderboard** — per-user request count, total storage, average GB per request, and optional Tautulli watch stats (plays, watch hours), each ranked against the full userbase
- **User detail pages** — click any user in the leaderboard to see their full profile: stat cards, an activity chart with 1W/1M/3M/1Y timeframes, and a complete request history sorted newest-first - **User detail pages** — click any user in the leaderboard to see their full profile: stat cards, an activity chart with 1W/1M/3M/1Y timeframes, and a complete request history sorted newest-first
- **Activity chart** — two modes: *Metrics* (requests, storage GB, watch hours as separate toggleable lines, with a Raw/Relative normalization toggle) and *Storage Load* (GB requested ÷ watch hours per bucket, with an all-time average reference line) - **Activity chart** — single metric picker (Requests · Storage · Watch Hours · Storage Load) × timeframe (1W/1M/3M/1Y), with both the user's own average and the server-wide average shown as reference lines. Storage Load is a running cumulative GB ÷ watch hours — a new request spikes the line up and subsequent watching drags it back down.
- **Alerting** — automatic alerts for stalled downloads, neglected requesters, and abusive patterns, with open/close state, notes, and auto-resolve when conditions clear - **Alerting** — automatic alerts for stalled downloads, neglected requesters, and abusive patterns, with open/close state, notes, and auto-resolve when conditions clear
- **Discord notifications** — posts a structured embed to a webhook whenever a new alert opens or a resolved one returns - **Discord notifications** — posts a structured embed to a webhook whenever a new alert opens or a resolved one returns
- **Settings UI** — configure all service URLs and API keys from the dashboard; no need to touch `.env.local` after initial setup - **Settings UI** — configure all service URLs and API keys from the dashboard; no need to touch `.env.local` after initial setup
@@ -20,29 +20,33 @@ Built with Next.js 16, TypeScript, and Tailwind CSS.
## Setup ## Setup
### 1. Clone and install ### Option A — Docker Compose (recommended)
Images are published to `gitea.thewrightserver.net/josh/oversnitch` on every push to `main` by the Gitea Actions workflow at [.gitea/workflows/build.yml](.gitea/workflows/build.yml).
On the deployment host:
```bash
# Pull the latest image and start
docker login gitea.thewrightserver.net # if the registry is private
docker compose pull
docker compose up -d
```
The bundled [docker-compose.yml](docker-compose.yml) mounts `./data` into `/app/data`, so `alerts.db` and `settings.json` persist across restarts. The dashboard listens on `http://localhost:3000` — after first boot, click the gear icon and enter your service URLs and API keys there.
To configure services via environment instead of the Settings UI, uncomment the relevant lines in `docker-compose.yml` (see the full list in *Option B*).
### Option B — Local Node
```bash ```bash
git clone https://gitea.thewrightserver.net/josh/OverSnitch.git git clone https://gitea.thewrightserver.net/josh/OverSnitch.git
cd OverSnitch cd OverSnitch
npm install npm install
npm run dev # or: npm run build && npm start
``` ```
### 2. Configure Configure through the Settings UI, or create `.env.local` with any of:
**Option A — Settings UI (recommended)**
Start the app and click the gear icon in the top-right corner. Enter your service URLs and API keys, hit **Test** to verify each connection, then **Save**.
```bash
npm run dev # or: npm run build && npm start
```
Settings are written to `data/settings.json` (gitignored).
**Option B — Environment variables**
Create `.env.local` in the project root. Values here are used as fallbacks when `data/settings.json` doesn't exist or doesn't contain an override.
```env ```env
# Required # Required
@@ -66,6 +70,19 @@ DISCORD_WEBHOOK=https://discord.com/api/webhooks/...
# NODE_TLS_REJECT_UNAUTHORIZED=0 # NODE_TLS_REJECT_UNAUTHORIZED=0
``` ```
Values in `data/settings.json` (written by the Settings UI) take precedence over env vars.
### CI / Registry
The Gitea workflow expects two repository-level config items:
| Kind | Name | Value |
|---|---|---|
| Variable | `REGISTRY_URL` | e.g. `gitea.thewrightserver.net` |
| Secret | `REGISTRY_TOKEN` | a Gitea access token with `write:package` scope |
On every push to `main`, the workflow builds a multi-stage Alpine image and pushes `:latest` + `:<sha7>` tags. Pushing a `v*` tag additionally publishes that tag.
--- ---
## Discord Notifications ## Discord Notifications
+10
View File
@@ -0,0 +1,10 @@
#!/bin/sh
set -e
# /app/data is usually a bind mount from the host, so whatever permissions the
# image set during build don't survive. Fix ownership on boot, then drop root.
if [ -d /app/data ]; then
chown -R nextjs:nodejs /app/data 2>/dev/null || true
fi
exec su-exec nextjs:nodejs "$@"