# 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` | `/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.