Upload files to "/"
This commit is contained in:
+78
@@ -0,0 +1,78 @@
|
||||
# ─────────────────────────────────────────────
|
||||
# QRknit — Dockerfile
|
||||
# Multi-stage build for a lean production image
|
||||
# ─────────────────────────────────────────────
|
||||
|
||||
# ── Stage 1: Build deps ───────────────────────
|
||||
FROM python:3.12-slim AS builder
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
gcc \
|
||||
libjpeg-dev \
|
||||
zlib1g-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --upgrade pip && \
|
||||
pip install --prefix=/install --no-cache-dir -r requirements.txt
|
||||
|
||||
|
||||
# ── Stage 2: Runtime image ────────────────────
|
||||
FROM python:3.12-slim AS runtime
|
||||
|
||||
LABEL maintainer="QRknit"
|
||||
LABEL description="Self-hosted URL shortener and QR code generator"
|
||||
LABEL version="1.0"
|
||||
|
||||
# Runtime system deps (Pillow needs libjpeg)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libjpeg62-turbo \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create non-root user
|
||||
RUN groupadd -r qrknit && useradd -r -g qrknit -d /app -s /sbin/nologin qrknit
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy installed Python packages from builder
|
||||
COPY --from=builder /install /usr/local
|
||||
|
||||
# Copy application files
|
||||
COPY --chown=qrknit:qrknit app.py .
|
||||
COPY --chown=qrknit:qrknit index.html .
|
||||
COPY --chown=qrknit:qrknit landing.html .
|
||||
COPY --chown=qrknit:qrknit landing-os.html .
|
||||
COPY --chown=qrknit:qrknit api-docs.html .
|
||||
COPY --chown=qrknit:qrknit static/ ./static/
|
||||
|
||||
# Data volume — SQLite DB lives here
|
||||
# Map to host path in Unraid: /mnt/user/appdata/qrknit → /app/data
|
||||
VOLUME ["/app/data"]
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
# Health check — waits 15s for startup before first check
|
||||
# Uses /api/health (public, no auth required)
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \
|
||||
CMD python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:5000/api/health')" || exit 1
|
||||
|
||||
USER qrknit
|
||||
|
||||
# Non-sensitive defaults only — SECRET_KEY and ADMIN_PASSWORD must be passed at runtime
|
||||
ENV BASE_URL=http://localhost:5000 \
|
||||
APP_NAME=to.ALWISP \
|
||||
ADMIN_USERNAME=admin \
|
||||
PORT=5000 \
|
||||
DEBUG=false \
|
||||
DB_PATH=/app/data/qrknit.db
|
||||
|
||||
# Start with Gunicorn
|
||||
CMD ["python3", "-m", "gunicorn", \
|
||||
"--workers", "1", \
|
||||
"--bind", "0.0.0.0:5000", \
|
||||
"--timeout", "60", \
|
||||
"--access-logfile", "-", \
|
||||
"--error-logfile", "-", \
|
||||
"app:app"]
|
||||
@@ -0,0 +1,80 @@
|
||||
# QRknit — Private Git Repository Setup
|
||||
|
||||
Notes for setting up and securing `https://git.qrknit.com/` to distribute the private instance source to Pro and Team plan customers.
|
||||
|
||||
---
|
||||
|
||||
## Recommended Stack: Gitea (or Forgejo)
|
||||
|
||||
Gitea is a lightweight self-hosted Git service — single binary, runs in Docker, low resource footprint. Forgejo is a community-maintained fork with the same API; either works.
|
||||
|
||||
**Why it fits:**
|
||||
- Private repos by default
|
||||
- Per-user access tokens (customers clone with a token, not a password)
|
||||
- Organisation/team support for managing customer access in bulk
|
||||
- Runs behind existing Cloudflare/Nginx Proxy Manager setup
|
||||
- Web UI for repo management
|
||||
- Gitea API allows automating account and token creation on subscription
|
||||
|
||||
---
|
||||
|
||||
## Docker Compose
|
||||
|
||||
```yaml
|
||||
services:
|
||||
gitea:
|
||||
image: gitea/gitea:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- GITEA__server__DOMAIN=git.qrknit.com
|
||||
- GITEA__server__ROOT_URL=https://git.qrknit.com
|
||||
- GITEA__server__DISABLE_SSH=true # HTTPS-only
|
||||
- GITEA__service__DISABLE_REGISTRATION=true # no public sign-ups
|
||||
- GITEA__service__REQUIRE_SIGNIN_VIEW=true # repo browser requires login
|
||||
volumes:
|
||||
- gitea-data:/data
|
||||
ports:
|
||||
- "3000:3000"
|
||||
```
|
||||
|
||||
Point Nginx Proxy Manager or Cloudflare proxy host at port 3000, issue a Let's Encrypt cert, done.
|
||||
|
||||
---
|
||||
|
||||
## Security Configuration
|
||||
|
||||
**Disable public registration** — the env var above does this, or set it in the admin panel after first run. Customer accounts are created manually or via the Gitea API.
|
||||
|
||||
**Keep the repo private** — set repository visibility to Private in Gitea. `REQUIRE_SIGNIN_VIEW=true` ensures the repo is not visible to unauthenticated requests.
|
||||
|
||||
**Per-customer access tokens** — when a customer subscribes to Pro/Team, create them a Gitea account and generate a personal access token scoped to `read:repository` only. Their clone URL:
|
||||
```
|
||||
https://<username>:<token>@git.qrknit.com/qrknit/qrknit.git
|
||||
```
|
||||
|
||||
**Revocation** — when a subscription lapses, disable or delete the Gitea account. Their token stops working immediately.
|
||||
|
||||
**Behind Cloudflare** — Cloudflare's WAF and rate limiting rules apply at the edge before any request reaches Gitea, which handles brute-force attempts on the token endpoint.
|
||||
|
||||
---
|
||||
|
||||
## What to Avoid
|
||||
|
||||
- **Public GitHub repo** — even a private GitHub repo exposes metadata and depends on GitHub's access controls rather than your own.
|
||||
- **SSH with shared keys** — harder to revoke per-customer without rotating keys for everyone.
|
||||
- **Embedding credentials in docs** — `INSTALL.md` shows the clone URL without credentials, which is correct. Credentials should be communicated out-of-band (welcome email, customer portal).
|
||||
|
||||
---
|
||||
|
||||
## Customer Onboarding Flow
|
||||
|
||||
1. Payment confirmed → create Gitea account (manually or via Gitea API)
|
||||
2. Generate a read-only personal access token (`read:repository` scope)
|
||||
3. Send welcome email with their clone URL and a link to `INSTALL.md`
|
||||
4. Subscription cancelled → disable or delete Gitea account
|
||||
|
||||
---
|
||||
|
||||
## Font Bundling Note (OFL-1.1)
|
||||
|
||||
If a future release bundles Google Font files locally (e.g. for air-gapped private instances), the OFL-1.1 licence requires the licence text to be included alongside the `.woff2` files. The current CDN delivery path has no such requirement. See `NOTICES.md` for full licence texts.
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
# 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
|
||||
- Country detection falls back to ip-api.com (free, no key required, <45 req/min)
|
||||
|
||||
**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 |
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
+2240
File diff suppressed because it is too large
Load Diff
+1061
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user