# Force Monitor — License Activation Guide This document explains how the licensing system works and how to activate **Force Monitor** after the trial period. --- ## 📋 Table of Contents 1. [How Licensing Works](#how-licensing-works) 2. [Trial Period](#trial-period) 3. [Getting Your Machine Fingerprint](#getting-your-machine-fingerprint) 4. [Activating Your License](#activating-your-license) 5. [License File Format](#license-file-format) 6. [Troubleshooting](#troubleshooting) 7. [For Vendors: Generating Licenses](#for-vendors-generating-licenses) --- ## How Licensing Works Force Monitor uses **Ed25519 cryptographic signatures** to verify licenses. The system supports: - **Trial mode** — free for a limited time (default: 7 days) - **Paid license** — perpetual or time-limited, tied to a specific machine ### Key Concepts | Term | Description | |------|-------------| | **Machine Fingerprint** | Unique hardware ID derived from your machine (MAC, motherboard serial, etc.) | | **Public Key** | Embedded in the app; used to verify license signatures | | **Private Key** | Kept secret by the vendor; used to sign licenses | | **Signed License** | JSON file cryptographically signed by the vendor | --- ## Trial Period When you first run Force Monitor, it automatically starts a **7-day trial**: ```bash # Check your current license status curl http://localhost:8080/api/license/status ``` **Example response (trial active):** ```json { "enabled": true, "mode": "trial", "locked": false, "message": "trial active: 6 day(s) remaining", "days_remaining": 6, "trial_started_at": "2026-04-20T17:00:00Z", "trial_expires_at": "2026-04-27T17:00:00Z", "fingerprint_short": "A1B2C3D4-...", "activation_configured": true } ``` ### Trial Expiration After the trial expires, if `require_after_trial: true` (default), the app will: - Return **HTTP 403 Forbidden** on all data endpoints - Show a license activation page at `http://localhost:8080/` - Display `"trial expired; activation required"` --- ## Getting Your Machine Fingerprint To request a license, you need your **machine fingerprint**. Get it via API or the web UI. ### Method 1: API (Recommended) ```bash curl -s http://localhost:8080/api/license/request | python3 -m json.tool ``` **Example response:** ```json { "app": "force_monitor", "version": "1.0.0", "generated_at": "2026-04-20T17:30:00Z", "hostname": "press-control-01", "platform": "linux/amd64", "fingerprint": "A1B2C3D4E5F6789012345678901234567890ABCDEF...", "fingerprint_short": "A1B2C3D4-E5F67890", "components": [ "machineid=1234567890abcdef...", "product_uuid=ABCDEF12-3456-7890-ABCD-EF1234567890", "hostname=press-control-01", "mac=aa:bb:cc:dd:ee:ff" ] } ``` ### Method 2: Web UI 1. Open `http://localhost:8080/` in your browser 2. If no `static/index.html` exists, a fallback page shows your **fingerprint** and **license mode** 3. Click **GET /api/license/request** to see the full activation request --- ## Activating Your License Once you receive a signed license from the vendor, activate it using one of these methods: ### Method 1: Web UI (Easiest) 1. Open `http://localhost:8080/` in your browser 2. Paste the signed license JSON into the textarea 3. Click **Activate license** ![Activation UI](docs/activation-ui.png) ### Method 2: API (cURL) ```bash # Save your license to a file cat > license.json << 'EOF' { "app": "force_monitor", "license_id": "LIC-2026-001", "customer": "Your Company Name", "fingerprint": "A1B2C3D4E5F6789012345678901234567890ABCDEF...", "issued_at": "2026-04-20T17:00:00Z", "expires_at": "2027-04-20T17:00:00Z", "features": [], "signature": "base64_encoded_ed25519_signature..." } EOF # Activate via API curl -X POST http://localhost:8080/api/license/activate \ -H "Content-Type: application/json" \ -d @license.json ``` **Success response:** ```json { "status": "activated", "license": { "mode": "licensed", "locked": false, "message": "license active", "customer": "Your Company Name", "license_id": "LIC-2026-001", "expires_at": "2027-04-20T17:00:00Z" } } ``` ### Method 3: Direct File Placement You can also place the license file directly: ```bash # Copy the signed license to the license directory cp license.json license/license.json # Restart the app ``` --- ## License File Format A valid signed license is a JSON file with this structure: ```json { "app": "force_monitor", "license_id": "LIC-2026-001", "customer": "Your Company Name", "fingerprint": "A1B2C3D4E5F6789012345678901234567890ABCDEF...", "issued_at": "2026-04-20T17:00:00Z", "expires_at": "2027-04-20T17:00:00Z", "features": ["premium", "mqtt"], "signature": "base64_encoded_ed25519_signature..." } ``` ### Field Descriptions | Field | Required | Description | |-------|----------|-------------| | `app` | ✅ | Must match `product_code` in config (`force_monitor`) | | `license_id` | ✅ | Unique license identifier | | `customer` | ✅ | Customer name | | `fingerprint` | ✅ | Must match the machine fingerprint exactly | | `issued_at` | ✅ | ISO 8601 timestamp when license was issued | | `expires_at` | ❌ | Optional expiration date (omitted = perpetual) | | `features` | ❌ | Optional feature flags array | | `signature` | ✅ | Ed25519 signature of the payload, base64-encoded | --- ## Troubleshooting ### "trial state invalid or tampered" **Cause:** System clock was changed, or trial files were modified. **Fix:** ```bash # Remove trial state and restart rm license/trial_state.json # Restart the app — a new trial will begin ``` ### "license fingerprint does not match this machine" **Cause:** The license was generated for a different machine. **Fix:** Generate a new activation request on this machine and request a new license. ### "invalid license signature" **Cause:** The license was not signed with the correct private key, or was corrupted. **Fix:** - Verify the license file wasn't modified - Ensure the vendor used the correct private key matching your `public_key_base64` ### "no license public key configured" **Cause:** `license.public_key_base64` is missing or invalid in `config.yaml`. **Fix:** Add the vendor's public key to your config: ```yaml license: enabled: true public_key_base64: "YOUR_BASE64_PUBLIC_KEY_HERE" ``` ### Check Current Status ```bash curl -s http://localhost:8080/api/license/status | python3 -m json.tool ``` --- ## For Vendors: Generating Licenses This repository includes helper functions for license generation. Use them in a **separate, private signing tool**. ### 1. Generate Ed25519 Key Pair ```go package main import ( "crypto/ed25519" "encoding/base64" "fmt" ) func main() { publicKey, privateKey, err := ed25519.GenerateKey(nil) if err != nil { panic(err) } fmt.Println("Public Key (base64):") fmt.Println(base64.StdEncoding.EncodeToString(publicKey)) fmt.Println("\nPrivate Key (base64) — KEEP SECRET:") fmt.Println(base64.StdEncoding.EncodeToString(privateKey)) } ``` ### 2. Sign a License Use the `SignLicenseWithPrivateKey` helper from `license.go`: ```go package main import ( "encoding/json" "fmt" "time" ) func main() { lic := SignedLicense{ App: "force_monitor", LicenseID: "LIC-2026-001", Customer: "Customer Name", Fingerprint: "A1B2C3D4E5F6789012345678901234567890ABCDEF...", IssuedAt: time.Now().UTC().Format(time.RFC3339), ExpiresAt: time.Now().AddDate(1, 0, 0).UTC().Format(time.RFC3339), Features: []string{}, } privateKeyBase64 := "YOUR_PRIVATE_KEY_BASE64" signedLic, err := SignLicenseWithPrivateKey(lic, privateKeyBase64) if err != nil { panic(err) } out, _ := json.MarshalIndent(signedLic, "", " ") fmt.Println(string(out)) } ``` ### 3. Distribute the Public Key Give customers the **public key** to add to their `config.yaml`: ```yaml license: enabled: true public_key_base64: "LLQ43Fle4nObHxmMQQsANvPUX5vDxx0TctpvQs+RI4s=" ``` --- ## Configuration Reference ```yaml license: enabled: true # Enable/disable licensing trial_days: 7 # Trial period duration require_after_trial: true # Lock app after trial expires data_dir: license # Directory for license files public_key_base64: "" # Ed25519 public key for verification product_code: force_monitor # App identifier (must match license) ``` --- ## API Reference | Endpoint | Method | Description | |----------|--------|-------------| | `/api/license/status` | GET | Get current license status | | `/api/license/request` | GET | Get activation request (fingerprint) | | `/api/license/activate` | POST | Submit signed license JSON | --- ## License This project is proprietary software. Contact your vendor for licensing inquiries.