Tonnage-app-IMCO/activator/readme.md
2026-04-20 18:01:16 +02:00

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

  1. How Licensing Works
  2. Trial Period
  3. Getting Your Machine Fingerprint
  4. Activating Your License
  5. License File Format
  6. Troubleshooting
  7. 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.

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

  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

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.