Manual v2.2.0 · App v2.2.1

Installation & setup guide

Everything you need to get SecuriReport AI running against your own Nginx logs, from a bare Node.js install through to Docker, configuration, and licensing. This app is a self-hosted, single-tenant tool — whoever installs it configures it; there's no separate admin-versus-end-user split.

Prerequisites

RequirementNeeded for
Node.js 22+Running locally without Docker
Docker & Docker ComposeRunning in a container (recommended for production)
An Nginx access/error log fileLog ingestion — a local path or a mounted volume
A running Ollama instance (optional)Local, offline AI report generation
A Gemini, OpenAI, and/or Anthropic API key (optional)Cloud AI report generation
A license key from your purchase emailClearing the "Unlicensed" warning banner

You don't need Ollama or any cloud API key to run the app. Without either configured, report generation automatically falls back to a deterministic, template-based engine — you always get a report.

Install with Node.js

  1. Install dependencies

    From the project root:

    npm install
    npm run dev

    This starts the Express backend (server.ts, via tsx), which also serves the frontend. By default the app listens on port 3000 (configurable via PORT once you build for production — see the note below).

  2. Build for production (optional)

    npm run build     # builds the Vite frontend and bundles the server
    npm start         # runs the bundled server (dist/server.cjs)

Install with Docker

  1. Copy the environment file

    cp .env.example .env
  2. Fill in the required security keys

    At minimum, set APP_API_KEY and VITE_APP_API_KEY — see Configuration below. Both are required before going to production.

  3. Point at your real log file

    Set NGINX_LOG_PATH (and optionally NGINX_ERROR_LOG_PATH) to your actual log file or a mounted volume.

  4. Build and start the container

    docker compose build   # required after changing VITE_APP_API_KEY or the Dockerfile
    docker compose up -d

    The container binds to 127.0.0.1:3015 on the host by default — not 0.0.0.0 — because it's designed to sit behind your own reverse proxy (Nginx, HAProxy, Caddy) with TLS, rather than face the internet directly.

Rebuild, don't just restart, after changing VITE_APP_API_KEY. It's baked into the frontend bundle at build time, not read at runtime — docker compose build is required, not just up.

The container always listens on port 3000 internally, regardless of the PORT variable. The host-facing port comes from the ports: mapping in docker-compose.yml (default 127.0.0.1:3015:3000) — change the left-hand side there for a different host port. The default log-directory mount is read-only, which is intentional (see Troubleshooting).

Configuration reference (.env)

All configuration lives in a single .env file at the project root. The variables below are the ones worth a real decision before going live; every other variable has a sensible default.

Log source

NGINX_LOG_PATH="/logs/access.log"
NGINX_ERROR_LOG_PATH="/logs/error.log"

Local AI (Ollama)

OLLAMA_BASE_URL="http://host.docker.internal:11434"
OLLAMA_MODEL="qwen2.5-coder:1.5b"

If Ollama runs on the Docker host itself, host.docker.internal is the correct hostname from inside the container. Only use instruction/chat-tuned models — base code-completion models were never trained to follow a system prompt and will produce off-topic output.

Cloud AI providers (all optional, independently configurable)

GEMINI_API_KEY="..."
OPENAI_API_KEY="..."
ANTHROPIC_API_KEY="..."

Any combination may be set. Each one becomes a selectable engine in the Report Generator's picker — you're never forced to enable all three.

Security — required in production

APP_API_KEY="change-me-generate-a-random-secret"
VITE_APP_API_KEY="change-me-generate-a-random-secret"

These two must be identical. Generate one with:

openssl rand -hex 32

APP_API_KEY is checked via the X-API-Key header on every state-changing route (log config, test-traffic injection, Ollama config, AI report generation, AI threat analysis). If unset, the server refuses those routes with a 503 in production.

ALLOWED_LOG_DIRS=""
OLLAMA_ALLOWED_HOSTS=""

Allow-lists that harden /api/logs/config (against arbitrary file reads) and /api/ollama/config (against SSRF). Leave blank unless you need to widen where these can point.

VariableDefaultDescription
PORT3000Documented but not read at runtime in Docker — use the ports: mapping instead.
TRUSTED_ADMIN_IPSemptySeeds the Firewall tab's whitelist on a browser's first-ever load. UI convenience only, not access control.
LOG_LEVELinfodebug logs connect time, first-byte latency and chunk counts per Ollama request.
OLLAMA_IDLE_TIMEOUT_MS240000Raise this on CPU-constrained hosts — prompt prefill can take well over a minute before the first token streams.
OLLAMA_HARD_CEILING_MS600000Absolute cap on one report generation, regardless of activity.
OLLAMA_NUM_PREDICT900Max tokens generated per report.
AUTH_USERSdisabledComma-separated username:password pairs — see Optional login screen.

Licensing & activation

This is a paid product, licensed and fulfilled entirely through Lemon Squeezy's built-in License Keys feature — there's no custom account system, webhook, or login screen involved on either side.

  1. Buy the license

    From this site's pricing section, which redirects to Lemon Squeezy checkout.

  2. Check your inbox

    Lemon Squeezy emails you a license key automatically as part of your purchase receipt — no code on our side is involved in issuing it.

  3. Set the key in your .env file

    APP_LICENSE_KEY="<the key from your receipt email>"
  4. Restart the app

    docker compose up -d --build
    # or, if running locally:
    npm start

There's no in-app "enter license key" screen — the key is a deploy-time configuration value, exactly like APP_API_KEY.

How you'll know it worked

SignalMeaning
Server log: [license] Valid license for <email>License accepted
Green "Licensed" badge next to Logout in the headerLicense is currently valid
Red pulsing "Unlicensed" badge + a red bannerKey is missing, invalid, or belongs to a different product

This is a soft check, not a hard lock. The app keeps working fully either way — running unlicensed simply means you're operating outside the terms of the license agreement.

Network access is required for licensing. At startup, and again every 24 hours, the app calls Lemon Squeezy's License API (api.lemonsqueezy.com). This is not an air-gapped/offline-capable feature.

Persist LICENSE_STATE_PATH. The first successful check creates an "activation instance" on Lemon Squeezy's side and caches its ID locally (default ./data/license-instance.json, mounted as a volume in docker-compose.yml). If that file is lost, every restart activates a brand-new instance instead of re-validating the existing one, and you'll eventually hit your license's activation limit. Don't remove the ./data:/app/data volume mount without understanding this.

Optional: the login screen

By default, the app has no login screen — anyone who can reach it on the network can use it. That's intentional: it's designed as an internal operator tool sitting behind your own network-level access control (an Nginx allow-list, a cloud firewall, a VPN), not as a public-facing service.

To add a login layer on top of that, set in .env:

AUTH_USERS="alice:secret1,bob:secret2"

and restart. This turns on a custom login screen, a signed HttpOnly session cookie with a 12-hour lifetime, and a Logout button in the header. Passwords are compared with a constant-time comparison to avoid leaking information via timing. This does not encrypt traffic itself — rely on it only over HTTPS, alongside your existing network-level controls, not instead of them.

Troubleshooting

SymptomLikely cause / fix
Red "Unlicensed" banner won't go awayAPP_LICENSE_KEY missing, invalid, wrong product, or the app can't reach api.lemonsqueezy.com. Check the [license] ... line in startup logs.
State-changing actions fail with a 503APP_API_KEY / VITE_APP_API_KEY not set, or the two values don't match. Rebuild after changing them in Docker.
"Inject Test Traffic" does nothingThe configured log path isn't writable — expected with the default read-only Docker mount. Point NGINX_LOG_PATH at a writable path if you need this.
A cloud AI provider doesn't appear in the engine pickerIts API key isn't set in .env, or the app wasn't restarted after setting it.
Report generation via Ollama times out or hangsRaise OLLAMA_IDLE_TIMEOUT_MS — cold model loads on CPU-constrained hosts can take well over a minute before the first token streams back.
Report generation silently falls back to the deterministic templateThe selected AI provider errored out (bad model, unreachable host, rate limit). This is the designed fail-safe, not a bug — check LOG_LEVEL=debug logs for the underlying error.
Can't reach the app at all in DockerCheck you're hitting 127.0.0.1:3015 or your reverse proxy — the container doesn't bind 0.0.0.0 by default.
Login screen appears unexpectedly, or doesn't appear when expectedControlled entirely by whether AUTH_USERS is set. Check .env and restart after any change.

Still stuck? This is a documentation-only license with no dedicated support line — but the FAQ and the assistant in the bottom-right corner of this site can point you to the right section.