# Jellomator

Dark dashboard for Arr* services and custom links.

## Features

- First-run admin setup
- Cookie-based admin auth
- Health endpoint at `/healthz`
- Readiness endpoint at `/readyz` (optional DB write probe)
- Public dashboard with search/filter
- Dedicated protected admin page at `/admin`
- Link CRUD backed by MariaDB
- Icon blobs stored in the database
- Containerized app deployment (requires MariaDB)
- Admin-managed service links
- Admin backup/export and restore with dry-run validation
- Structured JSON logs with request IDs (`x-request-id`)

## Local Dev

```bash
npm install
pip install -r backend/requirements.txt
npm run dev
```

Backend runs on `http://localhost:6363`.

Open `/admin` for the protected management page.
Ensure MariaDB is running and reachable by the backend `DB_*` variables.

## Docker

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

The app expects a MariaDB instance configured through environment variables.

### Health Endpoints

- `GET /healthz` returns `{"ok": true}` when the app process is up
- `GET /readyz` returns `{"ok": true}` when database checks pass
- `GET /readyz?write_test=true` additionally verifies DB writes using a temporary table

### Session and Cookie Env Vars

- `SESSION_TTL_SECONDS` (default: `86400`)
- `SESSION_ROTATE_SECONDS` (default: `3600`, rotate active session token when exceeded)
- `SESSION_COOKIE_SECURE` (default: `false`, set `true` in production HTTPS)
- `REQUIRE_CSRF` (default: `false`, checks same-origin/same-referer for write routes when enabled)
- `LOGIN_MAX_ATTEMPTS` (default: `5`)
- `LOGIN_WINDOW_SECONDS` (default: `300`)
- `LOGIN_LOCKOUT_SECONDS` (default: `900`)
- `MAX_NAME_LEN` (default: `255`)
- `MAX_CATEGORY_LEN` (default: `255`)
- `MAX_DESCRIPTION_LEN` (default: `2000`)
- `MAX_ICON_URL_LEN` (default: `2048`)
- `MAX_ICON_BYTES` (default: `2097152`)
- `USERNAME_MAX_LEN` (default: `64`)
- `PASSWORD_MIN_LEN` (default: `12`)

### Backup / Restore API

- `GET /api/admin/backup` exports users and links as JSON
- `POST /api/admin/restore?dry_run=true` validates a backup payload without applying
- `POST /api/admin/restore?dry_run=false` applies restore when body includes `"confirm": true`

## Gitea CI/CD

Add these secrets in Gitea:

- `REGISTRY`
- `REGISTRY_USERNAME`
- `REGISTRY_PASSWORD`
- `IMAGE_NAME`

The workflow builds and pushes `latest` plus the commit SHA tag.
