diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2f5ffe2 --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/GITREP.md b/GITREP.md new file mode 100644 index 0000000..35b355d --- /dev/null +++ b/GITREP.md @@ -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://:@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. diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..9303ed2 --- /dev/null +++ b/INSTALL.md @@ -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. diff --git a/index.html b/index.html new file mode 100644 index 0000000..512b59a --- /dev/null +++ b/index.html @@ -0,0 +1,2240 @@ + + + + + + +QRknit — Dashboard + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ 30D SLA + +
+ +
+
+ +
+
+ + + + +
+ + + +
+
+
+ +
+ + +
+
+

to.ALWISP

+
+
+ + +
+
+ + Advanced options +
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+
+
+ + +
+
+
+
QR
+
+

QR Code Ready

+

Download or customize in the QR tab.

+ +
+
+
+
+
+
+ + +
+
+
TOTAL LINKS
+
TOTAL CLICKS
+
CLICKS / 7D
+
AVG / LINK
+
+ + + + + +
+
+ + +
+
+ + + + +
+ +
+
CSV IMPORT — columns: url (required), custom_code, title, tags, expires_at
+ +
+ + + +
+
+
+ +
+ 0 selected + + + + + +
+ + +
+ + +
+
+
QR Generator
Generate and customize QR codes for any URL
+
+
+
+

Configure

+ + +
+
+
+
+
+
+ +
+ + + + + +
+
+
+ +
+ + + +
+ +
+ +
+
+

Preview

+
Enter a URL and click Generate
+ +
+
+
+ + + +
+ +
+
User Management
Create and manage user accounts
+ +
+ + + +

Loading...

+ + +
+
Organizations
Group users to share quotas, links, and tags
+ +
+ + + +

Loading...

+
+ +
+ +
+ + + + + + + + diff --git a/landing.html b/landing.html new file mode 100644 index 0000000..b099823 --- /dev/null +++ b/landing.html @@ -0,0 +1,1061 @@ + + + + + + +QRknit — URL Shortener & QR Code Generator with Analytics + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+ + + + +
+ + Open App → +
+
+ + + +
+
Managed · Cloud-native · Always on
+

Shorten. Track.
Convert.

+

+ A fully managed link shortening and QR code platform built for marketers, agencies, and teams. No servers. No ops. Just powerful analytics and branded short links — up and running in minutes. +

+
+ + +
+
+ Cloud hosting launching soon · No credit card required to join the waitlist +
+
+ + + +
+
+ +
Everything your team needs, nothing you don't
+
+ +
+
+ +
+

Smart Link Shortening

+

Custom codes, automatic expiry, tag organisation, and bulk CSV import and export. Search and filter your entire link library in milliseconds from any device.

+ Custom codes · Tags · Expiry · Bulk CSV +
+ +
+
+ +
+

QR Code Generator

+

Five dot styles, full colour control, and logo overlay support. Generate branded QR codes for any URL — campaigns, menus, print media — and download instantly.

+ 5 styles · Logo overlay · Custom colours +
+ +
+
+ +
+

Deep Click Analytics

+

Daily bar charts, hourly 7×24 activity heatmap, geographic country breakdown, and referrer and device tracking — per link, with raw CSV export for your own analysis.

+ Heatmap · Geo · Referrers · CSV export +
+ +
+
+ +
+

Team Management

+

Invite team members, set roles, and keep each user's links isolated. Admins get a full overview with owner attribution and one-click user-scoped filtering.

+ Role-based access · User isolation · Admin panel +
+ +
+
+ +
+

Custom Domains

+

Point your own branded short domain at QRknit and serve all your links from it. Consistent branding across every click, every QR scan, every campaign.

+ Branded domains · HTTPS · Auto-renew SSL +
+ +
+
+ +
+

REST API Access

+

Automate link creation, pull analytics data, and integrate QRknit into your own tools with a clean REST API. API key authentication and per-key rate limiting on Team plans.

+ REST API · API keys · Webhooks (coming) +
+ +
+
+
+ +
+ + +
+
+
+
+ +

Know exactly who clicked, when, and from where

+

Most link shorteners give you a click count. QRknit gives you the full picture — every event captured and broken down so you can make real decisions.

+
    +
  • Daily click bar chart per link (7d · 30d · 90d windows)
  • +
  • Hourly 7×24 activity heatmap — spot your peak times at a glance
  • +
  • Geographic country breakdown via Cloudflare headers
  • +
  • Referrer, device type, and browser breakdown
  • +
  • 30-day dashboard-wide click chart across all links
  • +
  • Raw click-event CSV export — every timestamp, referrer, country
  • +
+ +
+ + +
+
CLICK ANALYTICS — LAST 30 DAYS
+
+
HOURLY ACTIVITY
+
+
+
TOTAL CLICKS
+
PEAK DAY
+
COUNTRIES
+
+
+
+
+
+ +
+ + +
+
+
+ + + + +
+ +

Your entire link library. Organised and instant.

+

Everything lives in one clean dashboard. Create, edit, tag, and search across all your links — no page reloads, no friction.

+
    +
  • Custom short codes or auto-generated slugs
  • +
  • Tag-based organisation with one-click filtering
  • +
  • Inline search across codes, destinations, and tags
  • +
  • Pin your most important links to the top
  • +
  • Bulk tag, expire, or delete in seconds
  • +
  • CSV import to migrate an existing link library
  • +
+ +
+ +
+
+
+ +
+ + +
+
+ +
We handle the infrastructure. You handle the marketing.
+
+
+
99.9%
+
Uptime SLA
+

Your short links and QR codes are mission-critical. We operate redundant infrastructure with continuous health monitoring and guaranteed availability.

+
+
+
0
+
Ops overhead
+

No servers to provision, no Docker images to build, no SSL to renew. We handle every update, patch, and backup — so you never have to think about it.

+
+
+
+
Scale on demand
+

Traffic spike from a viral campaign? No problem. The platform scales automatically to handle your peak click volume without throttling or degraded performance.

+
+
+
+
+ +
+ + +
+
+ +
Built for teams that move fast
+
+ +
+
+ +
+

Marketing Teams

+

Run multi-channel campaigns with trackable short links and branded QR codes. See which channels convert and when your audience is most active.

+
    +
  • UTM parameter builder (coming)
  • +
  • Per-campaign analytics dashboards
  • +
  • Branded short domains for every campaign
  • +
  • Bulk link creation via CSV import
  • +
+
+ +
+
+ +
+

Agencies

+

Manage links and QR codes for multiple clients from a single workspace. Per-user isolation keeps client data separate, while admin tools give you full visibility.

+
    +
  • Multi-user with per-client isolation
  • +
  • Admin oversight across all accounts
  • +
  • White-label custom domains per client
  • +
  • Raw CSV export for client reporting
  • +
+
+ +
+
+ +
+

Developers

+

Integrate QRknit into your own products and workflows via REST API. Automate link creation, pull analytics programmatically, and build on top of the platform.

+
    +
  • Full REST API with API key auth
  • +
  • Programmatic link creation & management
  • +
  • Analytics data via API endpoints
  • +
  • Webhook support (coming)
  • +
+
+ +
+
+
+ +
+ + +
+
+ +
Simple, transparent pricing. No surprises.
+
+ +
+
Coming soon
+
Free
+
$0 / month
+

Try it out. No credit card, no commitment.

+
+
    +
  • 10 active links
  • +
  • 1,000 tracked clicks / month
  • +
  • Basic QR code generation
  • +
  • 7-day click analytics
  • +
  • Basic support
  • +
+ +
+ +
+
Coming soon
+
Starter
+
$9 / month
+

Perfect for individuals and small teams getting started with managed link shortening.

+
+
    +
  • Up to 500 active links
  • +
  • 50,000 tracked clicks / month
  • +
  • QR code generation (all styles)
  • +
  • 30-day click analytics
  • +
  • Referrer & device breakdown
  • +
  • CSV export
  • +
  • Email support
  • +
+ +
+ +
+ + +
+ +
+
Coming soon
+
Team
+
$79 / month
+

Advanced features and full API access for teams that need more insight and control.

+
+
    +
  • Unlimited links & clicks
  • +
  • Everything in Pro
  • +
  • Up to 3 custom short domains
  • +
  • Private instance
  • +
  • Unlimited team members
  • +
  • Full REST API + API key auth
  • +
  • UTM parameter builder
  • +
  • Password-protected links
  • +
  • Custom 404 & expired pages
  • +
  • Dedicated account support
  • +
+ +
+ +
+

All prices in USD · Billed monthly · Cancel any time · Annual plans available at launch

+
+
+ +
+ + +
+ +

Be first in line.

+

Cloud hosting is launching soon. Join the waitlist now and we'll reach out as soon as spots open up.

+
+ +
+
No credit card · No commitment · Priority account creation for waitlist members
+
+ + + + + + + +
+
+ +
+

Get in Touch

+

Send us a message and we'll get back to you.

+
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+
+
+
+

You're on the list!

+

Thanks for your interest. We'll be in touch as soon as spots open up.

+
+
+
+ + + +