# 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.