<!-- --------------------------------------------------------------------------- -->

<!-- Ordner: Docs -->

<!-- Datei: runbook.md -->

<!-- Relativer Pfad: Docs/runbook.md -->

<!-- --------------------------------------------------------------------------- -->

<!-- --------------------------------------------------------------------------- -->

<!-- --------------------------------------------------------------------------- -->

<!-- Ordner: Docs -->

<!-- Datei: runbook.md -->

<!-- Relativer Pfad: Docs/runbook.md -->

<!-- --------------------------------------------------------------------------- -->

# Runbook: Local Development vs Server Deployment

This runbook describes how to run the project locally (developer machine) and on the internal server.

The goal is a **clean separation** between:

- Local development (uses local NAS fixtures)
- Server deployment (uses the real NAS mount at `/mnt/niederlassungen`)

---

## 1. Repository Files and Separation

### 1.1 Compose files

- `docker-compose.yml`

  - Base compose file (server-like)
  - Mounts the real NAS path: `/mnt/niederlassungen:/mnt/niederlassungen:ro`

- `docker-compose.local.yml`

  - Local override
  - Mounts local fixtures: `./.local_nas:/mnt/niederlassungen:ro`

- `docker-compose.server-tools.yml` (optional)

  - Server-only tooling/override compose file.
  - Intended for additional server services or operational tooling.
  - Usually enabled on the server via `COMPOSE_FILE` or `-f` flags.

### 1.2 Env files

- Committed templates:

  - `.env.docker.example`
  - `.env.local.example`

- Local runtime env (not committed):

  - `.env.docker`

- Server runtime env (not committed):

  - `.env.server`

The compose setup uses:

- `ENV_FILE` to select which env file is loaded into the `app` container.

---

## 2. Local Development (Windows/macOS/Linux)

### 2.1 Prerequisites

- Docker Desktop installed and running.
- Project checked out.

### 2.2 Create local env file

Copy the template:

```bash
cp .env.docker.example .env.docker
```

Then edit `.env.docker`:

- Set a strong `SESSION_SECRET` (>= 32 characters)
- For local HTTP testing add:

```env
SESSION_COOKIE_SECURE=false
```

#### 2.2.1 Optional: Search provider configuration (RHL-016)

The Search backend supports two providers:

- `SEARCH_PROVIDER=fs` (default)

  - Local/test fallback.
  - Traverses the NAS-like folder structure directly.
  - Not intended to be fast on real NAS scale.

- `SEARCH_PROVIDER=qsirch`

  - Production/intended provider.
  - Uses QNAP Qsirch for indexed search.

If you set `SEARCH_PROVIDER=qsirch`, these env variables are required:

```env
SEARCH_PROVIDER=qsirch
QSIRCH_BASE_URL=http://<nas-ip>:8080
QSIRCH_ACCOUNT=<qsirch-user>
QSIRCH_PASSWORD=***
QSIRCH_PATH_PREFIX=/Niederlassungen
```

Optional Qsirch tuning:

```env
# Allowed: modified | created (case-insensitive)
QSIRCH_DATE_FIELD=modified

# Allowed: sync | async | auto (case-insensitive)
# Current implementation is sync-first.
# - "auto" currently behaves like "sync" (future-proof placeholder).
QSIRCH_MODE=sync
```

Notes:

- `scripts/validate-env.mjs` enforces these keys when `SEARCH_PROVIDER=qsirch`.
- Runtime normalizes Qsirch values (`trim`, `lowercase` for `QSIRCH_DATE_FIELD` / `QSIRCH_MODE`, and `trim` for `QSIRCH_PATH_PREFIX`) so behavior matches env validation.

### 2.3 Create local NAS fixtures

Create a minimal NAS tree:

```bash
mkdir -p ./.local_nas/NL01/2024/10/23
printf "dummy" > ./.local_nas/NL01/2024/10/23/test.pdf
```

### 2.4 Start the stack (local)

Run with the local override:

```bash
docker compose -f docker-compose.yml -f docker-compose.local.yml up --build
```

If you prefer running in the background:

```bash
docker compose -f docker-compose.yml -f docker-compose.local.yml up -d --build
```

> Note: The `app` container runs `node scripts/validate-env.mjs` automatically before `npm run start` (see `docker-compose.yml` `command:`).
> If required env vars are missing/invalid, the container fails fast and logs the validation error.

### 2.5 Verify health

```bash
curl -s http://localhost:3000/api/health
```

Expected (example):

- `db` is `ok`
- `nas.entriesSample` contains `NL01`

### 2.6 Create a test user in Mongo (manual)

Generate a bcrypt hash (local):

```bash
node -e "const bcrypt=require('bcryptjs'); console.log(bcrypt.hashSync('secret-password', 10))"
```

Open Mongo shell inside the DB container:

```bash
docker exec -it rhl-lieferscheine-db mongosh -u root -p supersecret --authenticationDatabase admin
```

In `mongosh`:

```js
use rhl-lieferscheine

db.users.insertOne({
  username: "branchuser",
  email: "nl01@example.com",
  passwordHash: "<PASTE_HASH_HERE>",
  role: "branch",
  branchId: "NL01",
  createdAt: new Date(),
  updatedAt: new Date()
})
```

### 2.7 Login + call protected endpoints (curl)

Login (stores cookie in `cookies.txt`):

```bash
curl -i -c cookies.txt \
  -H "Content-Type: application/json" \
  -d '{"username":"BranchUser","password":"secret-password"}' \
  http://localhost:3000/api/auth/login
```

Call endpoints with cookie:

```bash
curl -i -b cookies.txt http://localhost:3000/api/branches
curl -i -b cookies.txt http://localhost:3000/api/branches/NL01/years
curl -i -b cookies.txt http://localhost:3000/api/branches/NL01/2024/months
curl -i -b cookies.txt http://localhost:3000/api/branches/NL01/2024/10/days
curl -i -b cookies.txt "http://localhost:3000/api/files?branch=NL01&year=2024&month=10&day=23"
```

RBAC negative test (expected 403):

```bash
curl -i -b cookies.txt http://localhost:3000/api/branches/NL02/years
```

Logout:

```bash
curl -i -b cookies.txt -c cookies.txt http://localhost:3000/api/auth/logout
```

### 2.8 Manual end-to-end flow using the apiClient (RHL-008)

The repo contains a manual smoke-test script that exercises:

- authentication
- drill-down navigation (branches -> years -> months -> days -> files)
- negative cases (401/403/400/404)

Script:

- `scripts/manual-api-client-flow.mjs`

Run locally from your host machine:

```bash
node scripts/manual-api-client-flow.mjs \
  --baseUrl=http://localhost:3000 \
  --username=<user> \
  --password=<pw> \
  --branch=NL01
```

If your host machine does not have Node, you can also run it inside the app container:

```bash
docker compose exec app node scripts/manual-api-client-flow.mjs \
  --baseUrl=http://127.0.0.1:3000 \
  --username=<user> \
  --password=<pw> \
  --branch=NL01
```

> Note: You may see a Node warning about ESM detection (`MODULE_TYPELESS_PACKAGE_JSON`).
> The script still works and the warning is harmless.

### 2.9 Notes for Git Bash on Windows

Git Bash may rewrite paths like `/mnt/niederlassungen`.

If you need to run `docker exec ... ls /mnt/niederlassungen`, disable MSYS path conversion:

```bash
MSYS_NO_PATHCONV=1 docker exec -it rhl-lieferscheine-app ls -la /mnt/niederlassungen
```

---

## 3. Server Deployment (internal server)

### 3.1 Connect via SSH

```bash
ssh administrator@192.168.0.23
```

### 3.2 Prerequisites on the server

- Docker and Docker Compose installed.

- The real NAS share is mounted at:

  - `/mnt/niederlassungen`

- The mount is readable by Docker.

### 3.3 Create server env file

On the server (in the project folder), create `.env.server` based on the template:

```bash
cp .env.docker.example .env.server
```

Edit `.env.server`:

- Set a strong `SESSION_SECRET`.
- Keep `NODE_ENV=production`.
- If the app is served behind HTTPS (recommended): keep `SESSION_COOKIE_SECURE` unset (or `true`).

If the app is currently served over plain HTTP (no TLS), you may temporarily set:

```env
SESSION_COOKIE_SECURE=false
```

This is required because most clients will not send `Secure` cookies over HTTP.

> If the application is served over **plain HTTP** (no TLS), many clients will not send `Secure` cookies back.
> In that case, logins will appear to “work” (Set-Cookie is present), but subsequent requests will still be unauthenticated.

#### 3.3.1 Optional: Search provider configuration (RHL-016)

If the server should use Qsirch for search:

```env
SEARCH_PROVIDER=qsirch
QSIRCH_BASE_URL=http://<nas-ip>:8080
QSIRCH_ACCOUNT=<qsirch-user>
QSIRCH_PASSWORD=***
QSIRCH_PATH_PREFIX=/Niederlassungen

# Optional:
QSIRCH_DATE_FIELD=modified
QSIRCH_MODE=sync
```

Operational notes:

- `QSIRCH_BASE_URL` must be reachable **from inside the app container**.
- Values are treated case-insensitively by validation; runtime normalizes the same way.

### 3.4 Start the stack on the server

Use the base compose file only (no local override):

```bash
ENV_FILE=.env.server docker compose -f docker-compose.yml up -d --build
```

> If you configured a server-local `.env` file (see 3.4.1 / 3.4.2), you can use the short form:
>
> ```bash
> docker compose up -d --build
> ```

Optional (recommended): validate env inside the container after updating env files:

```bash
docker compose exec app node scripts/validate-env.mjs
```

### 3.4.1 Optional: Persist ENV_FILE selection via `.env`

If you want a simpler startup command (and to avoid forgetting `ENV_FILE=...`), you can create a small `.env` file **on the server only** that defines which env file Compose should use.

Create `./.env` in the project root:

```bash
printf "ENV_FILE=.env.server\n" > .env
```

After that, you can start the stack with:

```bash
docker compose -f docker-compose.yml up -d --build
```

Notes:

- Keep `.env` server-local (do not commit it).
- `.env.server` still contains secrets and must not be committed.
- Always run `docker compose` from the project root so Compose picks up the correct `.env` file.

### 3.4.2 Optional: Persist ENV_FILE + COMPOSE_FILE selection via `.env`

If your server setup uses multiple compose files (e.g. base + server tooling), you can also persist the compose file selection in the same **server-local** `.env` file.

Example `./.env` (server only, do not commit):

```env
ENV_FILE=.env.server
COMPOSE_FILE=docker-compose.yml:docker-compose.server-tools.yml
```

Notes:

- `COMPOSE_FILE` supports multiple files separated by `:` (common on Linux servers).
- Keep `.env` and `.env.server` server-local (do not commit).

After that, you can start/update the stack with:

```bash
docker compose up -d --build
```

Optional: verify which configuration Compose is actually using:

```bash
docker compose config
```

### 3.5 Verify

On the server:

```bash
curl -s http://127.0.0.1:3000/api/health
```

Expected:

- `db` is `ok`
- `nas.entriesSample` contains real branch folders (`NLxx`)

> Note: On some Linux servers, `localhost` resolves to IPv6 (`::1`).
> If `curl http://localhost:3000/api/health` fails, use `127.0.0.1` or `curl -4`.

### 3.6 Manual end-to-end flow (recommended inside container)

Run the manual smoke test inside the app container (no Node installation required on the host):

```bash
docker compose exec app node scripts/manual-api-client-flow.mjs \
  --baseUrl=http://127.0.0.1:3000 \
  --username=<user> \
  --password=<pw> \
  --branch=NL01
```

### 3.7 Logs and troubleshooting

Tail app logs:

```bash
docker compose -f docker-compose.yml logs --tail=200 app
```

Check container status:

```bash
docker compose ps
```

Common healthy state:

- `app` is `Up`
- `db` is `Up (healthy)`

> If you see the Node warning `MODULE_TYPELESS_PACKAGE_JSON`, it is currently expected and does not break runtime.

---

## 4. HTTPS Note (Future)

For real users, the application should be served over **HTTPS** (reverse proxy / TLS termination).

### 4.1 Why HTTPS matters here

- The session cookie (`auth_session`) is typically `Secure` in production.
- Browsers and many clients will **not send Secure cookies over HTTP**.
- Without HTTPS you may see:

  - Login response returns `200` and `Set-Cookie` appears
  - But subsequent API calls act unauthenticated (`/api/auth/me` returns `{ user: null }`)

### 4.2 Recommended setup

Run the Next.js container behind a reverse proxy that terminates TLS.

Common internal options:

- Nginx
- Caddy
- Traefik

Key requirements:

- External URL is HTTPS
- Proxy forwards requests to the app container (typically `http://127.0.0.1:3000`)

### 4.3 Cookie configuration rules

- Preferred production behavior:

  - `NODE_ENV=production`
  - `SESSION_COOKIE_SECURE` unset (or `true`)

- Temporary plain HTTP (not recommended):

  - set `SESSION_COOKIE_SECURE=false`

> Security note: Disabling Secure cookies on real networks is a risk.
> Use it only as a temporary workaround while HTTPS is being introduced.

### 4.4 Operational tip: host consistency for cookies

Cookies are scoped to the **host**.

- If you authenticate against `http://localhost:3000`, open links (including PDFs) on `http://localhost:3000`.
- If you authenticate against `http://127.0.0.1:3000`, also use that host.

Mixing hosts can look like “random 401s” because the browser will not send the cookie to a different host.

---

## 5. Appendix: Quick smoke checklist

### 5.1 Local

```bash
docker compose -f docker-compose.yml -f docker-compose.local.yml up -d --build
curl -s http://localhost:3000/api/health
npx vitest run
npm run build
```

### 5.2 Server

```bash
docker compose up -d --build
curl -s http://127.0.0.1:3000/api/health
docker compose exec app node scripts/validate-env.mjs
```
