# Operating the Isohedron License Server

This guide covers installing, configuring, and operating the license server — from initial setup through day-to-day administration.

You do **not** need Go, Docker, or any other development tools installed. The server is a single self-contained binary.

## Quick start

1. Extract the zip and cd into it.

2. Get your server ID and send it to your license provider.
```bash
./isohedron-license-server get-server-id
```

3. Once you receive .lic files, drop them into a licenses/ folder.
```bash
mkdir -p licenses
cp *.lic licenses/
```

4. Start the server.
`./isohedron-license-server -pubkey public.key`

The dashboard is at `http://localhost:8848`. That's it — clients can now check out licenses.


## What you should have received

| File | Description |
| --- | --- |
| `isohedron-license-server` | The server binary (or `isohedron-license-server.exe` on Windows) |
| `public.key` | The public key used to verify license files |
| `README.md` | This guide |
| `LICENSE.md` | Software license |
| `docker-compose.yml` | Docker Compose file for running the server in a container |
| `.env` | Configuration for Docker Compose (bind address, port, admin key) |

You may also have received `.lic` license files. If not, you'll request them after setting up the server (see Step 4 below).

## Setup

### 1. Create a directory for the server

Pick a location on the machine that will run the server. For example:

**Linux/macOS:**

```bash
sudo mkdir -p /opt/isohedron-license-server/licenses
```

**Windows:**

```
mkdir C:\isohedron-license-server\licenses
```

### 2. Copy files into place

Place the binary and public key into the directory:

```
/opt/isohedron-license-server/
├── isohedron-license-server       # the binary
├── public.key                     # the public key
└── licenses/                      # empty for now — licenses go here later
```

On Linux/macOS, make the binary executable. The `licenses/` directory must be writable by the server process (it stores lease data and lock files there):

```bash
chmod +x /opt/isohedron-license-server/isohedron-license-server
```

### 3. Choose a port and admin key, then start the server

**Port:** The server listens on port `8848` by default. Pick a port that isn't used by anything else on this machine. Your license provider may ask for this when creating server-bound licenses.

**Admin key:** Protects administrative operations (releasing stuck seats, purging expired licenses, reloading licenses, shutting down). You make this up yourself — it's just a password you pick. There's no registration or approval process. Pick something long and random:

```bash
# Linux/macOS — generate a random key
openssl rand -hex 32
```

Then start the server:

```bash
cd /opt/isohedron-license-server

./isohedron-license-server \
  -licenses ./licenses \
  -pubkey public.key \
  -addr :8848 \
  -admin-key "an-admin-key-you-make-up"
```

The server will start even without any license files. The dashboard at `http://your-server:8848/` will show a warning that no licenses are loaded — that's expected.

Client applications do **not** need any key. The signed license files are what controls access.

### 4. Request your licenses

Your license provider needs some information from you to create your licenses. Send them the following.

**Your server ID** (required for server-bound licenses):

Run this on the server machine. The ID is a hash unique to this machine and port:

```bash
isohedron-license-server get-server-id
# → iso-a7f3b2c1e9d4
```

If you're using a non-default port, pass `--port`:

```bash
isohedron-license-server get-server-id --port 9000
# → iso-f5e6d7c8b9a0
```

**Machine details for node-locked licenses** (if applicable):

For each workstation you want to lock a license to, find its hostname and system ID:

- **Hostname:** run `hostname` in a terminal on the machine
- **System ID (machine UUID):**
  - **Linux:** `cat /etc/machine-id`
  - **macOS:** `ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID`
  - **Windows:** run in PowerShell: `(Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Cryptography').MachineGuid`

Send the hostname and system ID for each machine to your license provider.

### 5. Install your licenses

When you receive `.lic` files from your provider, copy them into the `licenses/` directory and tell the server to reload:

```bash
cp *.lic /opt/isohedron-license-server/licenses/

isohedron-license-server reload --admin-key "your-admin-key"
```

The dashboard should now show your licensed products. You can also verify from the command line:

```bash
isohedron-license-server health
```

```json
{
  "status": "ok",
  "server_version": "0.1.0",
  "license_version": 1,
  "min_license_version": 1,
  "products": 1,
  "leases": 0
}
```

### 6. Run the smoke test

Verify that everything is working end-to-end — checkout, heartbeat, checkin, and cleanup:

```bash
isohedron-license-server test
```

### 7. Configure client machines

Client applications only need to know the server URL. Set the appropriate environment variable or configuration for your tool. For example, for Houdini HDAs:

```
ISOHEDRON_LICENSE_SERVER=http://your-server:8848
```

No API key is needed on client machines.

### 8. Adding more licenses later

Whenever you receive additional `.lic` files (more seats, new products, renewals):

1. Copy the new `.lic` files into the `licenses/` directory.
2. The server automatically detects the change and reloads within 60 seconds. To reload immediately:

```bash
isohedron-license-server reload --admin-key "your-admin-key"
```

Active sessions are not interrupted. New seats become available immediately.

## Configuration reference

| Flag | Default | Description |
| --- | --- | --- |
| `-licenses` | `./licenses` | Directory containing your `.lic` files |
| `-pubkey` | `public.key` | Path to the public key file |
| `-addr` | `:8848` | Address and port to listen on |
| `-admin-key` | *(none)* | Admin key for privileged operations (force-release, purge-expired, reload). You make this up. Alternatively, set `ISOHEDRON_ADMIN_KEY_FILE` to read the key from a file. |
| `-heartbeat` | `2m` | How often clients should send heartbeats |
| `-ttl` | `5m` | How long to wait before reclaiming a seat from a silent client |
| `-tls-cert` | *(none)* | Path to a TLS certificate file (enables HTTPS) |
| `-tls-key` | *(none)* | Path to a TLS private key file (enables HTTPS) |

## CLI commands

The server binary includes built-in commands for managing a running server. You don't need `curl` or any other tools.

### Release stuck seats

If a machine's seats are stuck (e.g., after a crash), you can release a specific lease by ID or all leases for a machine:

```bash
# Release a specific lease (get the ID from status)
isohedron-license-server force-release \
  --lease-id "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" \
  --admin-key "your-admin-key"

# Release all leases for a machine
isohedron-license-server force-release \
  --product my-product \
  --machine-id "WORKSTATION-A-7a8b9c0d" \
  --admin-key "your-admin-key"
```

### Get this server's ID

If your license provider needs to create a server-bound license, they'll ask for your server ID:

```bash
isohedron-license-server get-server-id
```

If you're using a non-default port, include `--port`:

```bash
isohedron-license-server get-server-id --port 9000
```

Send the output (e.g., `iso-a7f3b2c1e9d4`) to whoever creates your licenses.

### Check server health

Shows whether the server is running, how many products are licensed, and how many active leases there are. Also reports warnings if the licenses directory is missing or no licenses are loaded.

```bash
isohedron-license-server health
```

### View loaded licenses

See every license file the server has loaded, including its type, capacity, expiry date, days remaining, and whether it's active or expired. Useful for checking when renewals are needed.

```bash
isohedron-license-server licenses
```

### Purge expired licenses

Move expired license files out of the active licenses directory into an `expired/` subdirectory. The server reloads automatically afterward.

```bash
isohedron-license-server purge-expired --admin-key "your-admin-key"
```

You can also do this from the dashboard using the **Purge Expired** button.

### Reload license files

After adding new `.lic` files to the `licenses/` directory:

```bash
isohedron-license-server reload --admin-key "your-admin-key"
```

### Shut down the server

```bash
isohedron-license-server shutdown --admin-key "your-admin-key"
```

### View license status

Shows detailed information about each licensed product: how many floating seats are available and in use, how many node-locked machines are configured, who has what checked out, and when licenses expire.

See all products:

```bash
isohedron-license-server status
```

See a specific product:

```bash
isohedron-license-server status my-product
```

### Run a smoke test

Verify that everything is working — checkout, license proof, response signature, heartbeat, checkin, and cleanup:

```bash
isohedron-license-server test
```

All commands default to `http://localhost:8848`. To target a remote server, add `--server`:

```bash
isohedron-license-server status --server http://other-host:8848
```

## Running as a background service

### Linux (systemd)

Create `/etc/systemd/system/isohedron-license-server.service`:

```ini
[Unit]
Description=Isohedron License Server
After=network.target

[Service]
ExecStart=/opt/isohedron-license-server/isohedron-license-server \
  -licenses /opt/isohedron-license-server/licenses \
  -pubkey /opt/isohedron-license-server/public.key \
  -addr :8848 \
  -admin-key "an-admin-key-you-make-up"
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
WorkingDirectory=/opt/isohedron-license-server

[Install]
WantedBy=multi-user.target
```

Then:

```bash
sudo systemctl daemon-reload
sudo systemctl enable isohedron-license-server
sudo systemctl start isohedron-license-server
```

Check status:

```bash
sudo systemctl status isohedron-license-server
```

View logs:

```bash
journalctl -u isohedron-license-server -f
```

### Linux (supervisord)

If you're using [Supervisor](http://supervisord.org/) instead of systemd, create `/etc/supervisor/conf.d/isohedron-license-server.conf`:

```ini
[program:isohedron-license-server]
command=/opt/isohedron-license-server/isohedron-license-server
  -licenses /opt/isohedron-license-server/licenses
  -pubkey /opt/isohedron-license-server/public.key
  -addr :8848
  -admin-key "an-admin-key-you-make-up"
directory=/opt/isohedron-license-server
autostart=true
autorestart=true
stdout_logfile=/var/log/isohedron-license-server.log
stderr_logfile=/var/log/isohedron-license-server.log
```

Then:

```bash
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start isohedron-license-server
```

Check status:

```bash
sudo supervisorctl status isohedron-license-server
```

View logs:

```bash
tail -f /var/log/isohedron-license-server.log
```

### macOS (launchd)

Create `~/Library/LaunchAgents/com.isohedron.license-server.plist`:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.isohedron.license-server</string>
  <key>ProgramArguments</key>
  <array>
    <string>/opt/isohedron-license-server/isohedron-license-server</string>
    <string>-licenses</string>
    <string>/opt/isohedron-license-server/licenses</string>
    <string>-pubkey</string>
    <string>/opt/isohedron-license-server/public.key</string>
    <string>-addr</string>
    <string>:8848</string>
    <string>-admin-key</string>
    <string>an-admin-key-you-make-up</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
  <key>KeepAlive</key>
  <true/>
  <key>StandardOutPath</key>
  <string>/opt/isohedron-license-server/server.log</string>
  <key>StandardErrorPath</key>
  <string>/opt/isohedron-license-server/server.log</string>
</dict>
</plist>
```

Then:

```bash
launchctl load ~/Library/LaunchAgents/com.isohedron.license-server.plist
```

### Windows

The simplest approach is to use [NSSM](https://nssm.cc/) (the Non-Sucking Service Manager):

```
nssm install IsohedronLicenseServer C:\isohedron-license-server\isohedron-license-server.exe
nssm set IsohedronLicenseServer AppParameters -licenses C:\isohedron-license-server\licenses -pubkey C:\isohedron-license-server\public.key -addr :8848 -admin-key "an-admin-key-you-make-up"
nssm set IsohedronLicenseServer AppDirectory C:\isohedron-license-server
nssm set IsohedronLicenseServer AppStdout C:\isohedron-license-server\server.log
nssm set IsohedronLicenseServer AppStderr C:\isohedron-license-server\server.log
nssm start IsohedronLicenseServer
```

Or run it directly in a terminal:

```
cd C:\isohedron-license-server
isohedron-license-server.exe -licenses .\licenses -pubkey public.key -addr :8848 -admin-key "an-admin-key-you-make-up"
```

### Docker Compose

A `docker-compose.yml` and `.env` file are included in the distribution. Place them alongside the binary, public key, and licenses directory:

```
your-directory/
├── .env                          # configuration (bind address, port, admin key)
├── docker-compose.yml
├── isohedron-license-server      # the Linux binary
├── public.key
└── licenses/
    └── *.lic
```

Edit `.env` to configure the server:

```bash
# Which interface to accept connections on (0.0.0.0 = all, 127.0.0.1 = localhost only)
ISOHEDRON_BIND_ADDRESS=0.0.0.0

# Port to expose on the host
ISOHEDRON_PORT=8848

# Admin key for privileged operations (empty = no auth)
ISOHEDRON_ADMIN_KEY=an-admin-key-you-make-up

# Or read the admin key from a file instead (more secure):
# ISOHEDRON_ADMIN_KEY_FILE=/run/secrets/admin-key
```

Then start:

```bash
docker compose up -d
```

The server is available at `http://localhost:8848` (or whichever port you configured). Logs are accessible via `docker compose logs -f`.

To reload licenses after adding new `.lic` files:

```bash
isohedron-license-server reload --admin-key "your-admin-key"
```

To stop:

```bash
docker compose down
```

## Adding new license files

When you receive additional `.lic` files (e.g., more seats, new products):

1. Copy the new `.lic` file into the `licenses/` directory.
2. The server automatically detects the change and reloads within 60 seconds.

If you need the reload to take effect immediately, you can trigger it manually:

**CLI command:**

```bash
isohedron-license-server reload --admin-key "your-admin-key"
```

**Signal (Linux/macOS only):**

```bash
kill -HUP $(pidof isohedron-license-server)
```

**systemd (Linux):**

```bash
sudo systemctl reload isohedron-license-server
```

Active sessions are not interrupted. New seats become available immediately.

## Dashboard

Open `http://your-server:8848/` in a browser to see:

- Which products are licensed and how many seats are available
- Who currently has a license checked out
- How many days until the earliest license expires

The dashboard updates automatically every 30 seconds. No login is required.

## REST API endpoints

The server exposes a REST API on the configured address (default `:8848`). Admin endpoints require the `X-API-Key` header if `-admin-key` is set.

### Admin endpoints

| Method | Path | Description |
| --- | --- | --- |
| `POST` | `/v1/admin/force-release` | Force-release a lease by ID or by machine |
| `POST` | `/v1/admin/purge-expired` | Move expired license files to `expired/` subdirectory |
| `POST` | `/v1/admin/reload` | Hot-reload license files from disk |
| `POST` | `/v1/admin/shutdown` | Gracefully shut down the server |

### Informational endpoints

| Method | Path | Description |
| --- | --- | --- |
| `GET` | `/` | Web dashboard |
| `GET` | `/v1/health` | Health check (includes server version, product/lease counts) |
| `GET` | `/v1/licenses` | List all loaded license files with expiry status |
| `GET` | `/v1/metrics` | Prometheus-format metrics |
| `GET` | `/v1/server-id` | This server's hashed ID (for server-bound licenses) |
| `GET` | `/v1/status` | Query all products, seats, and active leases |
| `GET` | `/v1/version` | Server version |

See the [README](README.md#api-reference) for the full API reference, including client integration endpoints (checkout, checkin, heartbeat).

## Common issues

### Server won't start

- **"failed to load public key"** — The server can't find `public.key`. Check the path in your `-pubkey` flag.

### Server started but no licenses are loaded

- **"no license files found"** — The `licenses/` directory is empty or doesn't contain any `.lic` files. Make sure your license files have the `.lic` extension. The server runs without licenses but all checkouts will be denied.
- **"skipping license with bad signature"** — The `.lic` files were signed with a different key than the `public.key` you have. Contact whoever provided you with the files.
- **"skipping license bound to different server"** — A `.lic` file has a `server_id` that doesn't match this server. Run `isohedron-license-server get-server-id` to check your server's ID and confirm it matches what was used when the license was created.

### Clients can't connect

- Make sure the server is reachable from client machines (check firewalls, port 8848).
- Make sure clients are using the correct server URL (e.g., `http://your-server:8848`).

### Checkout rejected due to clock skew

- **"server clock appears to be more than 24h behind client"** — The server's system clock is significantly behind the client machine's clock. This check prevents license expiry bypass. Fix the server's clock (check NTP configuration). A skew of more than 12 hours logs a warning; more than 24 hours rejects the checkout.

### Seats are stuck / all in use

Crashed clients hold seats until the TTL expires (default: 5 minutes). To see who's holding seats, run:

```bash
isohedron-license-server status
```

The output includes a `leases` section for each product. Look for the stuck lease:

```json
{
  "products": [
    {
      "product": "my-product",
      "floating_seats": 5,
      "floating_in_use": 3,
      "leases": [
        {
          "lease_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
          "user": "ben",
          "machine_id": "WORKSTATION-A-7a8b9c0d",
          "product": "my-product",
          "lease_type": "floating",
          "checked_out_at": "2026-05-07T10:00:00Z",
          "processes": {
            "12345": {"pid": "12345", "last_heartbeat": "2026-05-07T10:02:00Z"}
          }
        }
      ]
    }
  ]
}
```

A lease with stale process heartbeats is likely stuck. Free it using its `id`:

```bash
isohedron-license-server force-release \
  --lease-id "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" \
  --admin-key "your-admin-key"
```

Or release all leases for that machine at once:

```bash
isohedron-license-server force-release \
  --machine-id "WORKSTATION-A-7a8b9c0d" \
  --admin-key "your-admin-key"
```

You can also check the dashboard at `http://your-server:8848/` which shows the same information visually.

### Server crashed — will I lose active leases?

No. Active leases are saved to `.leases` inside the licenses directory on every change. When the server restarts, it reloads them. Any leases that expired while the server was down are cleaned up automatically.

## Getting help

If you're seeing errors not covered here, check the server logs. All output is JSON, and the `msg` field describes what happened:

```bash
# Linux (systemd)
journalctl -u isohedron-license-server --since "5 minutes ago"

# Linux (supervisord)
tail -100 /var/log/isohedron-license-server.log

# macOS (launchd)
tail -100 /opt/isohedron-license-server/server.log

# Docker Compose
docker compose logs --since 5m

# Windows (NSSM) — if you configured NSSM with log files:
nssm get IsohedronLicenseServer AppStdout
# Then view that file, or check the Windows Event Viewer under
# Applications and Services Logs > IsohedronLicenseServer

# Windows (PowerShell) — if running with output redirected to a file:
Get-Content C:\isohedron-license-server\server.log -Tail 100

# Any platform — just look at the terminal where the server is running
```
