# QRknit — Installation & Configuration

## Prerequisites

- Docker installed and running
- A domain or subdomain pointed at your server (for public access)

Generate a strong `SECRET_KEY` before you begin:
```bash
python3 -c "import secrets; print(secrets.token_hex(32))"
```

---

## Option A — Docker run (quickest)

**1. Get the source**
```bash
git clone https://git.qrknit.com/qrknit.git
cd qrknit
```

**2. Build the image**
```bash
docker build -t qrknit:latest .
```

**3. Run the container**
```bash
docker run -d \
  --name qrknit \
  --restart unless-stopped \
  -p 5000:5000 \
  -v qrknit-data:/app/data \
  -e BASE_URL=https://yourdomain.com \
  -e APP_NAME=My.Links \
  -e SECRET_KEY=your-generated-key-here \
  -e ADMIN_PASSWORD=your-strong-password \
  qrknit:latest
```

Open `http://localhost:5000` (or your domain) and log in with `admin` / your `ADMIN_PASSWORD`.

---

## Option B — Docker Compose

**1. Get the source**
```bash
git clone https://git.qrknit.com/qrknit.git
cd qrknit
```

**2. Edit `docker-compose.yml`** — set at minimum:
```yaml
- BASE_URL=https://yourdomain.com
- APP_NAME=My.Links
- SECRET_KEY=your-generated-key-here
- ADMIN_PASSWORD=your-strong-password
```

**3. Build and start**
```bash
docker compose up -d --build
```

---

## Option C — Unraid (fresh install)

**Step 1 — Get the source onto Unraid**

Open the Unraid terminal and run:
```bash
cd /mnt/user/appdata
git clone https://git.qrknit.com/qrknit.git qrknit-src  # credentials provided with your Pro/Team licence
```

Or upload the source files manually to `/mnt/user/appdata/qrknit-src/`.

**Step 2 — Build the image**
```bash
cd /mnt/user/appdata/qrknit-src
docker build -t qrknit:latest .
```

> Private instance access (source repository credentials and licence key) is included with Pro and Team plans. Contact support if you have not received yours.

**Step 3 — Add the container in the Unraid Docker UI**

Go to **Docker** tab → **Add Container** and fill in:

| Field | Value |
|---|---|
| **Name** | `qrknit` |
| **Repository** | `qrknit:latest` |
| **Network Type** | `br0` (macvlan) or `Bridge` |
| **Port Mapping** | Host `5000` → Container `5000` *(not needed for macvlan)* |
| **Path (Volume)** | Host `/mnt/user/appdata/qrknit` → Container `/app/data` |

Add the following **Environment Variables**:

| Variable | Value | Notes |
|---|---|---|
| `BASE_URL` | `https://yourdomain.com` | Your public domain or subdomain |
| `APP_NAME` | `My.Links` | Display name shown in the UI — names with a dot (e.g. `to.mysite.io`) are split and styled automatically |
| `SECRET_KEY` | *(generated above)* | **Required** — signs session cookies |
| `ADMIN_PASSWORD` | *(your password)* | **Required** — admin account password |
| `ADMIN_USERNAME` | `admin` | Admin username (default: `admin`) |
| `COOKIE_SECURE` | `false` | Keep `false` behind Cloudflare or NPM. Set `true` only if Flask receives HTTPS directly. |
| `DEBUG` | `false` | Keep `false` in production |

Click **Apply**.

**Step 4 — Set up a reverse proxy** *(optional but recommended)*

**Cloudflare:**
- Point your DNS A record to your Unraid IP
- Enable **Always Use HTTPS** in Cloudflare dashboard (SSL/TLS → Edge Certificates)
- Keep `COOKIE_SECURE=false` — Cloudflare terminates TLS before Flask sees the request
- `CF-IPCountry` header is forwarded automatically — no extra configuration needed for geographic analytics

**Nginx Proxy Manager:**
- Add a proxy host: your domain → `unraid-lan-ip:5000`
- Issue a Let's Encrypt certificate on the SSL tab
- Keep `COOKIE_SECURE=false` — same reason as Cloudflare
- For country analytics without Cloudflare, mount a free MaxMind GeoLite2-Country database at `GEOIP_DB_PATH` (see Environment Variables), or set `GEO_API_FALLBACK=true` for non-commercial deployments

**Step 5 — Verify**
```bash
docker inspect --format='{{.State.Health.Status}}' qrknit
# healthy

curl https://yourdomain.com/api/health
# {"status":"ok"}
```

---

## Updating

Your data lives in the Docker volume and is preserved across updates.

**Docker / Docker Compose:**
```bash
cd /path/to/qrknit-src
git pull
docker build -t qrknit:latest .
docker stop qrknit && docker rm qrknit
# Re-run the same docker run command from installation
```

**Unraid:**
```bash
cd /mnt/user/appdata/qrknit-src
git pull
docker build -t qrknit:latest .
```
Then click **Force Update** on the container in the Docker tab.

> Sessions survive restarts as long as `SECRET_KEY` stays the same. Changing `SECRET_KEY` invalidates all active sessions — users will need to log in again.

---

## Environment Variables

| Variable | Default | Description |
|---|---|---|
| `BASE_URL` | `http://localhost:5000` | Public URL of your instance — used in short links and QR codes |
| `APP_NAME` | `My.Links` | Display name in the header, hero, login modal, and page title. Names with a dot are split and styled automatically. |
| `SECRET_KEY` | *(required)* | Signs session cookies — use a long random string |
| `ADMIN_PASSWORD` | *(required)* | Admin account password — upserted on every startup |
| `ADMIN_USERNAME` | `admin` | Admin account username |
| `PORT` | `5000` | Port Gunicorn listens on |
| `DEBUG` | `false` | Flask debug mode — keep `false` in production |
| `COOKIE_SECURE` | `false` | Set `true` only if Flask receives HTTPS directly (not behind a proxy) |
| `DB_PATH` | `/app/data/qrknit.db` | SQLite file path — leave as-is when using a Docker volume |
| `WEBHOOK_URL` | *(empty)* | Slack/Discord-compatible webhook — notified on new contact messages |
| `GEOIP_DB_PATH` | `/app/data/GeoLite2-Country.mmdb` | Local MaxMind GeoLite2 country database for click geolocation. Download from maxmind.com (free account) and mount into the data volume. Preferred over any network lookup. |
| `GEO_API_FALLBACK` | `false` | Enable ip-api.com lookups when no GeoLite2 DB is present. **Their free tier is licensed for non-commercial use only and rate-limited (~45 req/min)** — behind Cloudflare you don't need this (the `CF-IPCountry` header is used automatically). |
| `IP_ANONYMIZE` | `none` | Privacy policy for stored click IPs: `none`, `truncate` (zero the host part), or `hash` (SHA-256 — unique-visitor counting still works) |
| `CLICK_RETENTION_DAYS` | `0` | Purge click events older than N days (0 = keep forever) |
| `BACKUPS` | `true` | Daily SQLite backup via `VACUUM INTO` |
| `BACKUP_DIR` | `<db dir>/backups` | Where daily backups are written |
| `BACKUP_KEEP` | `7` | Number of daily backups to retain |

---

## Project Structure

```
qrknit/
├── app.py              # Flask backend — all routes and logic
├── index.html          # App SPA (inline CSS + JS, served at /app)
├── landing.html        # SaaS-focused landing page (default route /)
├── landing-os.html     # Private Instance landing page (served at /landing-os)
├── static/
│   └── qk-ico.png      # App icon (served at /static/qk-ico.png)
├── requirements.txt    # Python dependencies
├── Dockerfile          # Multi-stage Docker build
├── docker-compose.yml  # For non-Unraid deployments
├── .dockerignore
├── NOTICES.md
├── INSTALL.md
└── README.md
```

---

© 2025 QRknit. All rights reserved. This document and the accompanying software are proprietary. Redistribution or disclosure without a valid QRknit licence is prohibited.
