8.7 KiB
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
- How Licensing Works
- Trial Period
- Getting Your Machine Fingerprint
- Activating Your License
- License File Format
- Troubleshooting
- 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:
# Check your current license status
curl http://localhost:8080/api/license/status
Example response (trial active):
{
"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)
curl -s http://localhost:8080/api/license/request | python3 -m json.tool
Example response:
{
"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
- Open
http://localhost:8080/in your browser - If no
static/index.htmlexists, a fallback page shows your fingerprint and license mode - 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)
- Open
http://localhost:8080/in your browser - Paste the signed license JSON into the textarea
- Click Activate license
Method 2: API (cURL)
# 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:
{
"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:
# 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:
{
"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:
# 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:
license:
enabled: true
public_key_base64: "YOUR_BASE64_PUBLIC_KEY_HERE"
Check Current Status
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
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:
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:
license:
enabled: true
public_key_base64: "LLQ43Fle4nObHxmMQQsANvPUX5vDxx0TctpvQs+RI4s="
Configuration Reference
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.
