From 9eaed0aea4a650c77d3ca774046b4aadd4c1222f Mon Sep 17 00:00:00 2001 From: Antigravity Agent Date: Fri, 9 Oct 2026 07:34:09 +0200 Subject: [PATCH] chore(deploy): non-root image, reproducible builds, private database - Dockerfile: Go 1.26, npm ci, go mod verify, non-root user, alpine 3.22. - Compose: Postgres no longer published on the host, password and new security settings read from .env, GOMEMLIMIT. - README: document new environment variables and reading shortcuts. Co-Authored-By: Claude Opus 5.5 --- .gitignore | 6 ++++-- Dockerfile | 31 ++++++++++++++++--------------- README.md | 19 ++++++++++++++++++- docker-compose.unraid.yaml | 23 ++++++++++++++++++----- docker-compose.yaml | 20 ++++++++++++++++---- 5 files changed, 72 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index 352104d..f2b6b6b 100644 --- a/.gitignore +++ b/.gitignore @@ -26,14 +26,16 @@ Thumbs.db task.md -# Go +# Go (go.sum is committed for reproducible, verified builds) vendor/ -go.sum # Node node_modules/ npm-debug.log yarn-error.log +# Tooling +.playwright-mcp/ + # FlowReader Specific /_bmad-output/tmp/ diff --git a/Dockerfile b/Dockerfile index 5d9d998..7eabe33 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,32 +1,33 @@ # Multi-stage Dockerfile for FlowReader # Step 1: Build the React Frontend -FROM node:20-alpine AS web-builder +FROM node:22-alpine AS web-builder WORKDIR /app/web COPY web/package*.json ./ -RUN npm install +# Reproducible install from the lockfile +RUN npm ci --no-audit --no-fund COPY web/ ./ RUN npm run build # Step 2: Build the Go Backend -FROM golang:1.24-alpine AS builder +FROM golang:1.26-alpine AS builder WORKDIR /app -COPY . . -RUN go mod tidy -RUN go mod download -# Copy the built frontend from Step 1 -COPY --from=web-builder /app/web/dist ./web/dist -RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /server ./cmd/server +COPY go.mod go.sum ./ +RUN go mod download && go mod verify +COPY cmd/ ./cmd/ +COPY internal/ ./internal/ +RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-w -s" -o /server ./cmd/server # Step 3: Final Production Image -FROM alpine:3.19 +FROM alpine:3.22 WORKDIR /app -RUN apk add --no-cache wget ca-certificates +RUN apk add --no-cache wget ca-certificates tzdata \ + && adduser -D -H -u 10001 flowreader COPY --from=builder /server /app/server -# Copy the static files for the Go server to serve -COPY --from=builder /app/web/dist /app/web/dist -# Copy migration files for auto-migration -COPY --from=builder /app/migrations /app/migrations +# Static frontend and migrations (read-only for the app user) +COPY --from=web-builder /app/web/dist /app/web/dist +COPY migrations /app/migrations +USER flowreader EXPOSE 8080 CMD ["/app/server"] diff --git a/README.md b/README.md index 8aeffdf..67194db 100644 --- a/README.md +++ b/README.md @@ -87,13 +87,30 @@ Un template dédié est fourni pour les utilisateurs d'Unraid. | `PORT` | Port du serveur | `8080` | | `DATABASE_URL` | Connexion PostgreSQL | `postgres://...` | | `OPENROUTER_API_KEY` | Clé pour les résumés IA | *(Optionnel)* | +| `OPENROUTER_MODEL` | Modèle utilisé pour les résumés | `google/gemini-2.0-flash-001` | +| `POSTGRES_PASSWORD` | Mot de passe PostgreSQL (compose, à mettre dans `.env`) | `flowreader` — **à changer** | +| `REGISTRATION_ENABLED` | `false` pour fermer les inscriptions (le 1er compte, admin, reste possible) | `true` | +| `TRUSTED_PROXIES` | IP/CIDR du reverse proxy (ex. `172.16.0.0/12`) pour le rate-limit par IP client | *(vide)* | +| `COOKIE_SECURE` | Forcer le cookie `Secure` (HTTPS derrière proxy) | auto | +| `WS_ALLOWED_ORIGINS` | Origines WebSocket supplémentaires | *(vide)* | + +> 🔒 La base PostgreSQL n'est plus exposée sur l'hôte par défaut. Changez `POSTGRES_PASSWORD` +> **avant** le premier démarrage (il n'est appliqué qu'à la création du volume). + +### Lecture + +- Raccourcis clavier : `j`/`k` naviguer, `o` ouvrir, `m` lu/non lu, `s` favori, `v` original, + `/` rechercher, `u` non lus/tous, `r` actualiser, `Espace` page suivante puis article suivant, + `?` aide. +- Bouton **Aa** dans le lecteur : thème (clair, sépia, sombre, auto), police (serif, sans, + Atkinson Hyperlegible), taille, interligne et largeur de colonne. ## 🛠️ Développement Envie de mettre les mains dans le code ? ```bash -# Pré-requis : Go 1.22+, Node 20+, Docker +# Pré-requis : Go 1.26+, Node 22+, Docker # 1. Lancer les services (DB) make docker-up diff --git a/docker-compose.unraid.yaml b/docker-compose.unraid.yaml index 9dc9023..6000002 100644 --- a/docker-compose.unraid.yaml +++ b/docker-compose.unraid.yaml @@ -10,8 +10,19 @@ services: - "8080:8080" environment: - PORT=8080 - - DATABASE_URL=postgres://flowreader:flowreader@db:5432/flowreader?sslmode=disable - - OPENROUTER_API_KEY=votre_clef_ici + # Set a strong password in a .env file next to this compose file. + # Note: POSTGRES_PASSWORD only applies when the volume is first created. + - DATABASE_URL=postgres://flowreader:${POSTGRES_PASSWORD:-flowreader}@db:5432/flowreader?sslmode=disable + # Close public sign-ups once your account exists (the first account is admin). + - REGISTRATION_ENABLED=${REGISTRATION_ENABLED:-true} + # Behind a reverse proxy (Traefik, NPM, Caddy…): its IP/CIDR, so rate + # limiting sees real client IPs. Leave empty when exposed directly. + - TRUSTED_PROXIES=${TRUSTED_PROXIES:-} + # Force Secure cookies when served over HTTPS behind a proxy. + - COOKIE_SECURE=${COOKIE_SECURE:-} + - GOMEMLIMIT=48MiB + # Optional: AI summaries (OpenRouter). Leave empty to disable. + - OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-} depends_on: db: condition: service_healthy @@ -25,13 +36,15 @@ services: container_name: flowreader-db environment: - POSTGRES_USER=flowreader - - POSTGRES_PASSWORD=flowreader + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-flowreader} - POSTGRES_DB=flowreader volumes: # Persistance des données sur Unraid (chemin typique) - /mnt/user/appdata/flowreader/postgres_data:/var/lib/postgresql/data - ports: - - "5432:5432" + # Not published on the host: only the app container needs the database. + # Uncomment for local debugging only (and bind to 127.0.0.1). + # ports: + # - "127.0.0.1:5432:5432" restart: unless-stopped healthcheck: test: [ "CMD-SHELL", "pg_isready -U flowreader -d flowreader" ] diff --git a/docker-compose.yaml b/docker-compose.yaml index 8b799bc..6da1acd 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -7,7 +7,17 @@ services: - "8080:8080" environment: - PORT=8080 - - DATABASE_URL=postgres://flowreader:flowreader@db:5432/flowreader?sslmode=disable + # Set a strong password in a .env file next to this compose file. + # Note: POSTGRES_PASSWORD only applies when the volume is first created. + - DATABASE_URL=postgres://flowreader:${POSTGRES_PASSWORD:-flowreader}@db:5432/flowreader?sslmode=disable + # Close public sign-ups once your account exists (the first account is admin). + - REGISTRATION_ENABLED=${REGISTRATION_ENABLED:-true} + # Behind a reverse proxy (Traefik, NPM, Caddy…): its IP/CIDR, so rate + # limiting sees real client IPs. Leave empty when exposed directly. + - TRUSTED_PROXIES=${TRUSTED_PROXIES:-} + # Force Secure cookies when served over HTTPS behind a proxy. + - COOKIE_SECURE=${COOKIE_SECURE:-} + - GOMEMLIMIT=48MiB depends_on: db: condition: service_healthy @@ -27,12 +37,14 @@ services: image: postgres:16-alpine environment: - POSTGRES_USER=flowreader - - POSTGRES_PASSWORD=flowreader + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-flowreader} - POSTGRES_DB=flowreader volumes: - postgres_data:/var/lib/postgresql/data - ports: - - "5432:5432" + # Not published on the host: only the app container needs the database. + # Uncomment for local debugging only (and bind to 127.0.0.1). + # ports: + # - "127.0.0.1:5432:5432" restart: unless-stopped healthcheck: test: [ "CMD-SHELL", "pg_isready -U flowreader -d flowreader" ]