387 lines
12 KiB
Markdown
387 lines
12 KiB
Markdown
# TechDesk Licensing — Vendor Operations Guide
|
||
|
||
This document covers everything you need to do as the vendor:
|
||
setting up the license server, generating keys, publishing the app to customers,
|
||
and issuing keys. Read it top to bottom the first time, then jump to the relevant
|
||
section for ongoing operations.
|
||
|
||
---
|
||
|
||
## Table of Contents
|
||
|
||
1. [One-Time Setup (Do This First)](#1-one-time-setup-do-this-first)
|
||
2. [Deploy the License Server](#2-deploy-the-license-server)
|
||
3. [Publish TechDesk to a Customer](#3-publish-techdesk-to-a-customer)
|
||
4. [Issue a Key — Current Customer (First Time)](#4-issue-a-key--current-customer-first-time)
|
||
5. [Customer Applies the Key](#5-customer-applies-the-key)
|
||
6. [Renew an Expiring Key](#6-renew-an-expiring-key)
|
||
7. [Ongoing Operations Checklist](#7-ongoing-operations-checklist)
|
||
|
||
---
|
||
|
||
## 1. One-Time Setup (Do This First)
|
||
|
||
This section is done **once, ever**. It generates the RSA key pair that secures
|
||
all license keys. Do it on a machine you control — your development machine or
|
||
the server where the license server will run.
|
||
|
||
### Step 1.1 — Install dependencies
|
||
|
||
```bash
|
||
cd license_server
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
### Step 1.2 — Generate the RSA key pair
|
||
|
||
```bash
|
||
python generate_keys.py
|
||
```
|
||
|
||
Output:
|
||
```
|
||
Key pair generated successfully.
|
||
Private key : private_key.pem (KEEP SECRET)
|
||
Public key : public_key.pem (safe to embed in TechDesk)
|
||
```
|
||
|
||
Two files are created:
|
||
|
||
| File | What it is | Who sees it |
|
||
|---|---|---|
|
||
| `private_key.pem` | Signs all license keys | You only. Never leave this machine. |
|
||
| `public_key.pem` | Verifies license keys | Embedded in TechDesk. Customers have it. |
|
||
|
||
> **Critical:** `private_key.pem` must NEVER be committed to git, sent to
|
||
> customers, or included in any build. If it leaks, anyone can forge unlimited
|
||
> valid license keys. Back it up securely (encrypted drive, password manager).
|
||
|
||
### Step 1.3 — Embed the public key in TechDesk
|
||
|
||
Open `app/services/license_service.py` and find the `PUBLIC_KEY_PEM` constant
|
||
near the top. Replace the placeholder with the full contents of `public_key.pem`:
|
||
|
||
```python
|
||
PUBLIC_KEY_PEM = """-----BEGIN PUBLIC KEY-----
|
||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
|
||
(your actual key here)
|
||
...
|
||
-----END PUBLIC KEY-----"""
|
||
```
|
||
|
||
Include the `-----BEGIN PUBLIC KEY-----` and `-----END PUBLIC KEY-----` lines.
|
||
|
||
> After this change, TechDesk will validate license keys against your real
|
||
> public key. Before this step, all keys fail and the app runs in Community mode.
|
||
|
||
### Step 1.4 — Commit the public key change
|
||
|
||
```bash
|
||
git add app/services/license_service.py
|
||
git commit -m "Embed RSA public key for license validation"
|
||
```
|
||
|
||
This is the only license-related change that goes into the customer-facing
|
||
codebase. `private_key.pem` stays off git permanently — add it to `.gitignore`:
|
||
|
||
```bash
|
||
echo "license_server/private_key.pem" >> .gitignore
|
||
echo "license_server/licenses.db" >> .gitignore
|
||
git add .gitignore
|
||
git commit -m "Ignore license server secrets from git"
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Deploy the License Server
|
||
|
||
The license server is a small Flask app that runs **on your machine or a private
|
||
server**. It does not need to be reachable from the internet — you only access
|
||
it when generating new keys.
|
||
|
||
### Option A — Run locally on your development machine (simplest)
|
||
|
||
Good for low volume (fewer than 50 customers). Run it only when you need to
|
||
generate a key, not 24/7.
|
||
|
||
```bash
|
||
cd license_server
|
||
|
||
# Set a strong admin password (or set the env var in your shell profile)
|
||
export LICENSE_SERVER_PASSWORD="your-strong-password-here"
|
||
|
||
python app.py
|
||
```
|
||
|
||
Open `http://127.0.0.1:5001` in your browser. Log in with the password you set.
|
||
|
||
### Option B — Run on a private VPS (recommended for production)
|
||
|
||
If you prefer a persistent server so you can generate keys from anywhere:
|
||
|
||
1. Copy the `license_server/` directory to your VPS (via `scp` or git)
|
||
2. Copy `private_key.pem` to the VPS **via a secure channel** (not email)
|
||
3. Set environment variables:
|
||
|
||
```bash
|
||
export LICENSE_SERVER_PASSWORD="your-strong-password-here"
|
||
export SECRET_KEY="random-64-char-string-here"
|
||
```
|
||
|
||
4. Run with gunicorn behind nginx (or just run directly if access is restricted
|
||
to your IP via firewall):
|
||
|
||
```bash
|
||
pip install -r requirements.txt
|
||
python app.py
|
||
```
|
||
|
||
5. Restrict access to your IP in the VPS firewall — port 5001 should never be
|
||
publicly reachable.
|
||
|
||
> **Important:** The license server has no multi-user auth, no HTTPS, and no
|
||
> rate limiting. It is designed for internal use only. Never expose port 5001
|
||
> to the internet.
|
||
|
||
---
|
||
|
||
## 3. Publish TechDesk to a Customer
|
||
|
||
When you deliver TechDesk to a new customer, you give them the codebase.
|
||
Here is exactly what to include and exclude.
|
||
|
||
### What to include
|
||
|
||
Everything in the project **except** the items listed below.
|
||
|
||
### What to EXCLUDE
|
||
|
||
| Path | Why |
|
||
|---|---|
|
||
| `license_server/private_key.pem` | Signing key — leaking it lets anyone forge keys |
|
||
| `license_server/licenses.db` | Your customer database — not their business |
|
||
| `license_server/` (entire folder) | Customers don't need the server |
|
||
| `.env` | Contains your secrets, not theirs |
|
||
| `logs/` | Your logs |
|
||
| `app/static/uploads/` | Your data |
|
||
| `migrations/` (optional) | Only needed if they'll run migrations manually |
|
||
|
||
### Recommended delivery method
|
||
|
||
**Option A: Git archive (cleanest)**
|
||
|
||
```bash
|
||
# From your project root — produces a zip with no git history and no excluded files
|
||
git archive --format=zip --output=techdesk-v1.0.zip HEAD
|
||
```
|
||
|
||
Then manually remove `license_server/` from the zip if it's tracked in git:
|
||
|
||
```bash
|
||
zip -d techdesk-v1.0.zip "license_server/*"
|
||
```
|
||
|
||
**Option B: Manual zip**
|
||
|
||
```bash
|
||
# Exclude everything sensitive
|
||
zip -r techdesk-v1.0.zip . \
|
||
--exclude "*.git*" \
|
||
--exclude "*.env*" \
|
||
--exclude "license_server/*" \
|
||
--exclude "logs/*" \
|
||
--exclude "app/static/uploads/*"
|
||
```
|
||
|
||
**Checklist before sending:**
|
||
|
||
- [ ] `app/services/license_service.py` contains the real public key (not the placeholder)
|
||
- [ ] `license_server/` is not in the archive
|
||
- [ ] No `.env` file in the archive
|
||
- [ ] No `private_key.pem` anywhere in the archive
|
||
|
||
---
|
||
|
||
## 4. Issue a Key — Current Customer (First Time)
|
||
|
||
Your existing customer is running TechDesk without a license key right now
|
||
(Community mode). Here is how to issue them a key.
|
||
|
||
### Step 4.1 — Start the license server
|
||
|
||
```bash
|
||
cd license_server
|
||
export LICENSE_SERVER_PASSWORD="your-password"
|
||
python app.py
|
||
```
|
||
|
||
Open `http://127.0.0.1:5001` and log in.
|
||
|
||
### Step 4.2 — Fill in the Generate form
|
||
|
||
| Field | What to enter |
|
||
|---|---|
|
||
| Customer / Company Name | Their company name (e.g. `LT Services Inc`) |
|
||
| Contact Email | Their admin's email address |
|
||
| Tier | `Business` (recommended starting tier) |
|
||
| Duration | `1 year` (365 days) — gives you an annual renewal cycle |
|
||
|
||
Click **Generate Key**.
|
||
|
||
### Step 4.3 — Copy the key
|
||
|
||
The full key appears on screen immediately after generation, like:
|
||
|
||
```
|
||
TDESK-eyJjdXN0b21lciI6Ikx....(long string)....
|
||
```
|
||
|
||
**Copy the entire key** — it is shown in full only once on screen. The database
|
||
stores only a short preview for your records. If you lose it, generate a new one.
|
||
|
||
### Step 4.4 — Send the key to the customer
|
||
|
||
Send the key via email, Slack, or your preferred channel. Include:
|
||
|
||
- The full `TDESK-...` key string
|
||
- The expiry date (1 year from today)
|
||
- A link to the setup instructions below (or paste them directly)
|
||
|
||
**Example email:**
|
||
|
||
> Subject: Your TechDesk License Key
|
||
>
|
||
> Hi [Name],
|
||
>
|
||
> Your TechDesk Business license key is below. It is valid until [expiry date].
|
||
>
|
||
> ```
|
||
> TDESK-eyJjdXN0b21lciI6...
|
||
> ```
|
||
>
|
||
> To activate:
|
||
> 1. Open your `.env` file on the TechDesk server
|
||
> 2. Add this line: `LICENSE_KEY=TDESK-eyJ...` (the full key)
|
||
> 3. Restart TechDesk: `sudo systemctl restart gunicorn`
|
||
> 4. Visit Admin → License to confirm activation
|
||
>
|
||
> Let me know if you have any questions.
|
||
|
||
### Step 4.5 — Verify the key was recorded
|
||
|
||
Back in the license server UI, confirm the new row appears in the Issued Licenses
|
||
table with the correct customer name, tier, and expiry date. This is your audit
|
||
trail.
|
||
|
||
---
|
||
|
||
## 5. Customer Applies the Key
|
||
|
||
Share these instructions with the customer. They only need to do this once (and
|
||
again on renewal).
|
||
|
||
### Customer instructions
|
||
|
||
**On the TechDesk server:**
|
||
|
||
1. Open the `.env` file in the TechDesk installation directory:
|
||
```bash
|
||
nano /home/it-ticket/myapp/.env
|
||
```
|
||
|
||
2. Add or update this line (paste the full key you received):
|
||
```
|
||
LICENSE_KEY=TDESK-eyJjdXN0b21lciI6...
|
||
```
|
||
|
||
3. Save the file and restart TechDesk:
|
||
```bash
|
||
sudo systemctl restart gunicorn
|
||
```
|
||
|
||
4. Verify activation — log in to TechDesk as admin and go to
|
||
**Admin → License**. The page should show:
|
||
- A **Business** (or Enterprise) tier badge
|
||
- Your company name and email
|
||
- The expiry date
|
||
- Green checkmarks on all licensed features
|
||
|
||
5. The warning banner at the top of the page (if it was showing) will disappear.
|
||
|
||
**If something goes wrong:**
|
||
|
||
| Symptom | Likely cause | Fix |
|
||
|---|---|---|
|
||
| Banner still shows "No license key" | `.env` not saved or service not restarted | Re-check `.env`, restart gunicorn |
|
||
| Banner shows "License key invalid" | Key was truncated or modified during copy | Re-copy the full key, ensure no line breaks |
|
||
| License page shows "Public key not configured" | The public key placeholder was not replaced | Contact vendor — the app build is missing the public key |
|
||
|
||
---
|
||
|
||
## 6. Renew an Expiring Key
|
||
|
||
30 days before expiry the customer will see a blue banner: _"Your license expires
|
||
in X days."_ When expiry passes, it turns to a yellow warning and features lock
|
||
down to Community tier.
|
||
|
||
### Renewal process
|
||
|
||
1. Start the license server (same as Step 4.1)
|
||
2. Generate a new key for the customer (same as Step 4.2–4.3)
|
||
- Use the same customer name, email, and tier
|
||
- Choose the same duration (1 year)
|
||
3. Send the new key to the customer (same as Step 4.4)
|
||
4. Customer replaces the old `LICENSE_KEY=` line in `.env` with the new key and
|
||
restarts — same as Step 5
|
||
|
||
> The old key and new key are independent — the customer simply overwrites the
|
||
> `.env` value. There is no overlap or grace period to manage on your end.
|
||
|
||
---
|
||
|
||
## 7. Ongoing Operations Checklist
|
||
|
||
### When you generate a new version of TechDesk
|
||
|
||
- [ ] Ensure `app/services/license_service.py` still contains the real `PUBLIC_KEY_PEM`
|
||
(it should, unless you accidentally reverted it)
|
||
- [ ] Exclude `license_server/` and `.env` from the customer build
|
||
- [ ] Test: start the app with no `LICENSE_KEY` → confirm Community mode banner appears
|
||
- [ ] Test: start the app with a valid key → confirm correct tier and features
|
||
|
||
### When a customer reports a licensing issue
|
||
|
||
1. Ask them to go to **Admin → License** and send you a screenshot
|
||
2. Check the reason shown:
|
||
- `no_key` → they haven't added the `.env` line or haven't restarted
|
||
- `expired` → they need a renewal key
|
||
- `invalid_signature` → the key was corrupted in transit; re-send it
|
||
- `no_public_key` → their build is missing the public key; send a fixed build
|
||
|
||
### If `private_key.pem` is ever compromised
|
||
|
||
All previously issued keys remain valid (you cannot invalidate them — offline
|
||
model has no revocation). Your only recourse:
|
||
|
||
1. Generate a new RSA key pair (`python generate_keys.py` after deleting the old files)
|
||
2. Embed the new `public_key.pem` in TechDesk
|
||
3. Re-issue keys to all existing customers
|
||
4. Send them the updated build with the new public key
|
||
|
||
This is the main limitation of offline keys — treat `private_key.pem` with the
|
||
same care as a master password.
|
||
|
||
### Backing up `private_key.pem`
|
||
|
||
Store at least two encrypted copies:
|
||
- A password manager (Bitwarden, 1Password, etc.) as a secure note
|
||
- An encrypted USB drive stored offline
|
||
|
||
If you lose `private_key.pem` you cannot issue new keys and cannot recover
|
||
existing ones — you would need to do a full key rotation (same as the compromise
|
||
scenario above).
|
||
|
||
---
|
||
|
||
*Last updated: 2026-05-22*
|