# Unity Devcontainer Licensing (Codespaces and Local Dev Containers)

This guide explains how to configure Unity licensing for `scripts/unity/run-unity-docker.sh` in Codespaces and local dev containers.

The script supports three paths:

1. Personal online activation (`UNITY_EMAIL` + `UNITY_PASSWORD`)
2. Manual `.ulf` activation (`UNITY_LICENSE`) for serial-based licenses
3. Pro serial activation (`UNITY_SERIAL` + `UNITY_EMAIL` + `UNITY_PASSWORD`)

It also persists Unity license cache artifacts between container runs using:

- `${UNITY_TEST_PROJECT_DIR}/.unity-license-cache/local-share-unity3d` -> `/root/.local/share/unity3d`
- `${UNITY_TEST_PROJECT_DIR}/.unity-license-cache/config-unity3d` -> `/root/.config/unity3d`

By default, this is under `/home/vscode/.unity-test-project/.unity-license-cache` in the devcontainer.
You can override it with `UNITY_LICENSE_CACHE_DIR` if needed.

## Recommended Setup for Personal License

Use online activation. Unity Personal does not support manual activation via `.alf` / `.ulf` upload.

### Codespaces Secrets

Set up secrets in GitHub first:

1. Open the repository on GitHub.
2. Go to `Settings` -> `Secrets and variables`.
3. Choose `Codespaces` (for Codespaces-only secrets) or `Actions` (for repository-level secrets reused by workflows).
4. Select `New repository secret` and add each value below.

Reference: [Managing encrypted secrets for your codespaces](https://docs.github.com/en/codespaces/managing-your-codespaces/managing-your-account-specific-secrets-for-github-codespaces)

Codespaces injects these secrets as environment variables inside the devcontainer.
`run-unity-docker.sh` reads them and forwards them into the Unity container via
Docker `-e` flags. Resulting license artifacts are persisted in the cache mounts.

Add these secrets:

- `UNITY_EMAIL`
- `UNITY_PASSWORD`

### Local Dev Container

Use one of these methods:

- Export environment variables before running scripts.
- Use `scripts/unity/setup-license.ps1` to generate `.unity-secrets/credentials.env`.

`run-unity-docker.sh` auto-loads `.unity-secrets/credentials.env` when environment variables are not already set.

## Recommended Setup for Pro License

Set all of these values:

- `UNITY_SERIAL`
- `UNITY_EMAIL`
- `UNITY_PASSWORD`

For Pro, `UNITY_SERIAL` is required. If serial credentials are incomplete, the script fails early with a clear error.

## Activation Behavior and Fallback Rules

For Personal online activation:

1. Script attempts online activation with `UNITY_EMAIL` and `UNITY_PASSWORD`.
2. Script classifies failures as either hard licensing rejection or transient/network failure.
3. For hard rejection (for example `Found 0 entitlement groups`, `com.unity.editor.headless was not found`, invalid credentials), script fails fast.
4. If Unity reports `No license activation found for this computer` or `No ULF license found`, the script surfaces that as a machine-registration problem and stops. Unity Personal cannot recover through manual `.alf` upload.
5. If activation is still not successful, script exits with an actionable error.

For manual `.ulf` activation:

1. This path is only for serial-based licenses.
2. The `.ulf` file must be generated for the same machine identity.
3. `UNITY_LICENSE` can be loaded from the environment or `.unity-secrets/license.ulf`.

For all activation methods, script verifies that at least one local license artifact exists before running Unity commands.

## Post-Activation Verification

The script checks for at least one of these files:

- `/root/.local/share/unity3d/Unity/Unity_lic.ulf`
- `/root/.config/unity3d/Unity/Unity_lic.ulf`
- `/root/.local/share/unity3d/Unity/UnityEntitlementLicense.xml`
- `/root/.config/unity3d/Unity/UnityEntitlementLicense.xml`

If none exists, the script fails early and points to this guide.

For personal online activation, the script also persists the raw activation log under:

- `/root/.config/unity3d/.activation-<timestamp>.log`

Because `/root/.config/unity3d` is mounted from host cache, that log persists and can be attached to support tickets.

## Entitlement Mismatch Symptoms

If the account does not have a compatible entitlement for the requested activation path, Unity logs often contain:

- `Found 0 entitlement groups`
- `No valid Unity Editor license found`

Typical causes:

- Wrong Unity account for your organization/license.
- Personal account trying a Pro-only entitlement path.
- Expired or unavailable entitlement.

Actions:

1. Verify the Unity account can activate the expected license type.
2. If using Personal, do not switch to manual activation; Unity Hub is the only supported activation path.
3. If using Pro, verify `UNITY_SERIAL`, `UNITY_EMAIL`, and `UNITY_PASSWORD` together.
4. Re-run the script and confirm license artifacts are created in the mounted cache paths.

## Headless Mode Notes

For editor automation in CI/devcontainers, this repo already runs Unity with:

- `-batchmode`
- `-nographics`
- `-quit`

Those are the correct headless command-line flags for non-interactive workflows.

There is no required "headless toggle" in the Unity dashboard for this script path;
errors like `Found 0 entitlement groups` indicate a licensing-service decision for
the current account/machine activation, not missing CLI flags.

If you see these errors:

```text
Found 0 entitlement groups and 0 free entitlements matching requested entitlement ids
Error: 'com.unity.editor.headless' was not found.
```

the issue is usually Unity licensing state for this account/machine combination,
not missing command-line flags.

### Dedicated Server vs Editor Automation

- Dedicated Server build target is for built player binaries.
- This repo is running `unity-editor` in headless mode for compile/test automation.
- So Dedicated Server target does not replace editor license activation requirements.

### What To Do Instead

1. Keep using `-batchmode -nographics -quit` (already done in scripts).
2. Ensure credentials are correct (`UNITY_EMAIL`, `UNITY_PASSWORD`).
3. If using a serial-based `.ulf`, regenerate it for the same machine context (see next section).
4. If Unity still returns `Found 0 entitlement groups` or `com.unity.editor.headless`, open a Unity support ticket and include full activation logs.

### Why This Matters

Headless flags control runtime/editor behavior, but license acceptance is still validated by Unity services.
When services return no usable activation, local cache never gets populated and subsequent runs fail.

## Important: ULF Files are Machine-Specific

`.ulf` license files are **encrypted with machine-specific hardware identifiers**.

### Why ULF Fallback Fails with "Machine bindings don't match"

If you see this error:

```text
Machine bindings don't match
```

Your `.ulf` file was generated on a different machine or with different hardware.
ULF files cannot be ported across:

- Different computers
- Different Docker containers (each container is a unique "machine")
- Different Codespaces instances

### What to Do

**For local dev containers**: Use online activation (`UNITY_EMAIL` + `UNITY_PASSWORD`) instead.
Once successful, the license artifact is cached and persists across container rebuilds.

**For Codespaces**: Store `UNITY_EMAIL` and `UNITY_PASSWORD` in GitHub Secrets.
Each Codespaces instance will regenerate the license on first run.

**Generating new ULF files**: If you absolutely need a `.ulf` file for a serial-based license:

1. Generate an `.alf` with `npm run unity:generate-activation`
2. Complete Unity's manual activation flow with your serial
3. That `.ulf` will only work on this specific machine

Unity Personal cannot use this manual activation path.

### Understanding the ULF Fallback Logic

The script can consume a `.ulf` file when one is provided. **This should only succeed if:**

1. The `.ulf` was just generated moments ago in a _previous_ Docker run on _this machine_
2. The Docker container still has the same hardware ID (unlikely after rebuild)

In practice, ULF fallback will almost always fail with "Machine bindings don't match" if:

- You're using `.ulf` from a different dev machine
- You're trying `.ulf` in a new Codespaces instance
- You rebuilt the devcontainer (new container = new machine binding)

For Unity Personal, focus on online activation instead. Manual activation is not supported.

### Why Online Activation is Recommended

Online activation (email + password) is superior because:

- It generates a machine-specific license inside the container each time
- The license artifact is automatically cached between runs
- After first successful activation, subsequent runs don't re-authenticate
- Persists across Docker image rebuilds (cache is preserved)
- Works identically in Codespaces, CI/CD, and local dev containers

## Machine Not Registered

### When This Happens

If Unity logs contain either of these messages:

```text
No license activation found for this computer
No ULF license found
```

the current machine identity is not registered with your Unity account.
This typically occurs on first run in a new Docker container or Codespaces instance
when online activation cannot issue a license for the current machine.

### If You Are Using Unity Personal

Unity's current licensing docs state that manual activation does not work with Unity Personal.
In this repo, `run-unity-docker.sh` treats this as a stop condition and points you back to supported options:

1. Activate through Unity Hub on a supported interactive machine.
2. If you already have a machine-matched `.ulf` from a serial-based manual activation, place it at `.unity-secrets/license.ulf`.
3. If this is unexpected for a Personal account, attach the saved activation log to a Unity support request.

### Manual Activation Workflow for Serial-Based Licenses Only

If you are using a paid license with a serial key, this repo still supports manual activation:

1. **The `.alf` file is automatically placed at** `.unity-secrets/manual-activation.alf`.
2. **Upload it** at <https://license.unity3d.com/manual> (log in with your Unity account).
3. **Enter your serial** in Unity's manual activation flow, then download the `.ulf` file and save it as `.unity-secrets/license.ulf`.
4. **Run**: `npm run unity:retry-license`

### Dedicated Generation Command

If for any reason the `.alf` was not auto-generated (for example, if the run was interrupted),
generate it explicitly:

```bash
npm run unity:generate-activation
```

This runs `scripts/unity/generate-activation.sh`, which requires `UNITY_SERIAL`, spins up a Docker container,
calls `-createManualActivationFile`, and copies the result to `.unity-secrets/manual-activation.alf`.

### Retry Command

After placing `.unity-secrets/license.ulf`, run:

```bash
npm run unity:retry-license
```

`scripts/unity/retry-license.sh`:

- Validates that `.unity-secrets/license.ulf` is present.
- Clears stale cached license artifacts so Unity re-reads the new file.
- Re-runs `compile.sh` with the new license loaded.

## Quick Troubleshooting Checklist

1. **Check for licensing-state errors**: If output contains `Found 0 entitlement groups` or `com.unity.editor.headless not found`, capture full logs and treat it as a Unity licensing-service/account-machine issue.
2. Confirm secrets/env vars are present in the shell running the script.
3. Confirm `.unity-secrets/credentials.env` and `.unity-secrets/license.ulf` are present if using file-based loading.
4. Check script output for entitlement mismatch strings.
5. **Verify credentials persist**: After first successful activation, run `ls -la ~/.unity-test-project/.unity-license-cache/` to confirm cache artifacts exist.
6. **For Docker image rebuilds**: If you rebuild the devcontainer, the cache persists but `post-create.sh` fixes permissions automatically on next run.
7. Confirm `.unity-test-project/.unity-license-cache/` directories are writable.
8. If output contains `No license activation found for this computer` or `No ULF license found`, treat it as a machine-registration problem. For Unity Personal, do not use manual activation; for serial-based licenses, follow the [Machine Not Registered](#machine-not-registered) section.

### Quick Validation with npm Script

Before running expensive Docker operations, validate your setup quickly:

```bash
npm run unity:validate
```

This script checks (without Docker):

- ✓ Credentials are available (env vars or `.unity-secrets/`)
- ✓ License files exist and are readable
- ✓ Cache directory is writable
- ✓ Docker daemon is running
- ✓ Required scripts are in place

It provides clear output showing what's missing and next steps.

Additional scripts for manual activation:

- `npm run unity:generate-activation` — generates `.unity-secrets/manual-activation.alf` for serial-based manual activation workflows.
- `npm run unity:retry-license` — validates `.unity-secrets/license.ulf` is present, clears stale cached artifacts, and re-runs the compile command for serial-based manual activation.

### Manual Troubleshooting Steps

If `npm run unity:validate` passes but compilation still fails, try:

```bash
bash scripts/unity/run-unity-docker.sh -batchmode -nographics -quit -projectPath /project -logFile -
```

## Credential Persistence Across Docker Rebuilds

When you rebuild the devcontainer:

1. The devcontainer is stopped and a new one is built from scratch
2. **Your credentials (`UNITY_EMAIL`, `UNITY_PASSWORD`) are preserved** in `.unity-secrets/credentials.env`
3. **Your license cache is preserved** in `/home/vscode/.unity-test-project/.unity-license-cache/` (Docker persistent volume)
4. The `post-create.sh` script runs automatically and fixes directory permissions
5. Next compilation run:
   - Loads credentials from `.unity-secrets/credentials.env`
   - Mounts the persistent cache into the new container
   - Reuses the cached license artifact (no re-authentication needed)
   - If cache is empty, performs fresh online activation on first run

**This means**: After successful first-time activation, subsequent compilations **reuse the cached license without re-authenticating**, greatly reducing startup overhead. Network access is still used to validate the license is still active, but you won't experience multiple authentication prompts.

### What Persists vs. What Doesn't

| Item                                          | Persists? | Details                                             |
| --------------------------------------------- | --------- | --------------------------------------------------- |
| `.unity-secrets/credentials.env`              | ✓ Yes     | Kept in workspace .gitignore, survives all rebuilds |
| `.unity-license-cache/` directory             | ✓ Yes     | Docker persistent volume, survives rebuilds         |
| License artifact files (.ulf / .xml)          | ✓ Yes     | Stored in persistent cache volume                   |
| `/root/.local/share/unity3d` inside container | ✗ No      | Recreated from persistent volume on each Docker run |
| Docker image layers                           | ✗ No      | Rebuilt each time, but cached locally               |

**Key takeaway**: **Credentials and cache both persist, enabling fast reuse**

### If Cache Doesn't Persist

If you see `Token not found in cache` repeatedly:

1. First activation likely failed silently (check for entitlement errors above)
2. Run `ls -la ~/.unity-test-project/.unity-license-cache/` to check if any artifacts exist
3. If cache is empty, verify credentials are correct and regenerate machine-matched activation data
4. Delete cache and retry: `rm -rf ~/.unity-test-project/.unity-license-cache/`
5. Rerun compilation and check full output for error messages
