# Install a standalone Gateway on Linux

This guide installs **Particle Horizon `keystone-gateway`** on Linux from the `.tar.gz` release archive.

Complete [host preparation](https://portal.cjcit.io/knowledge-base/keystone_gateway/0.2/getting-started/prerequisites/) first, and obtain the archive for the host architecture, a signed license, and a 32-byte state-key file. This procedure uses a private HTTP listener only to provide a copyable minimal installation; use HTTPS/mTLS for traffic beyond a tightly controlled host or network.

## Supported Linux architectures

Release archives are produced for:

<table><caption class="sr-only">Supported Linux architectures</caption><thead><tr><th scope="col">Architecture</th><th scope="col">Archive suffix</th></tr></thead><tbody><tr><td>AMD64 / x86-64</td><td><code>Linux_x86_64.tar.gz</code></td></tr><tr><td>ARM64 / AArch64</td><td><code>Linux_arm64.tar.gz</code></td></tr></tbody></table>

The archive contains the following runtime layout:

```
particle_horizon/
├── SOFTWARE/
│   └── keystone-gateway-<version>-linux-<arch>/
│       ├── BUILD_INFO.txt
│       ├── THIRDPARTY_NOTICES
│       └── bin/
│           └── keystone-gateway
├── keystone-gateway --> SOFTWARE/keystone-gateway-<version>-linux-<arch>/bin/keystone-gateway
├── local_config/
│   └── keystone-gateway/
│       └── gateway.yaml
├── local_data/
│   └── keystone-gateway/
└── logs/
    └── keystone-gateway/
```

The intended installation root on Linux is:

```
/opt/particle_horizon
```

Confirm the host architecture with:

```
uname -m
```

Typical results are `x86_64` or `aarch64`.

## Procedure

### 1\. Extract the archive

Create a temporary extraction directory and unpack the release:

```
INSTALL_TMP="$(mktemp -d)"
tar -xzf "${ARCHIVE}" -C "${INSTALL_TMP}"
find "${INSTALL_TMP}/particle_horizon" -maxdepth 4 -print
```

### 2\. Create the service account

```
sudo groupadd --system keystone-gateway
sudo useradd \
  --system \
  --gid keystone-gateway \
  --home-dir /opt/particle_horizon \
  --shell /usr/sbin/nologin \
  keystone-gateway
```

If the account already exists, `useradd` reports that condition and no further action is required.

### 3\. Install the Gateway

Copy the extracted `particle_horizon` tree into the installation root:

```
sudo mkdir -p /opt/particle_horizon
sudo cp -a "${INSTALL_TMP}/particle_horizon/." /opt/particle_horizon/
```

The Gateway is now available through the stable path, which points at the binary beneath the version-specific `SOFTWARE` directory:

```
/opt/particle_horizon/keystone-gateway version
```

### 4\. Set ownership and install the state key

The archive supplies `local_config`, `local_data`, and `logs`. Configuration holds secret material, so keep it owned by root and readable only by the service group; state, report, and log directories are written by the service account. Replace `PATH/TO/...` with transferred release material; do not generate or echo a key in a shell history.

```
sudo chown -R root:keystone-gateway /opt/particle_horizon/local_config/keystone-gateway
sudo chmod 0750 /opt/particle_horizon/local_config/keystone-gateway
sudo chmod 0640 /opt/particle_horizon/local_config/keystone-gateway/gateway.yaml
sudo install -d -o keystone-gateway -g keystone-gateway -m 0700 \
  /opt/particle_horizon/local_data/keystone-gateway/state \
  /opt/particle_horizon/local_data/keystone-gateway/reports
sudo chown -R keystone-gateway:keystone-gateway /opt/particle_horizon/logs/keystone-gateway
sudo chmod 0750 /opt/particle_horizon/logs/keystone-gateway
sudo install -o root -g keystone-gateway -m 0640 PATH/TO/gateway-state-key \
  /opt/particle_horizon/local_config/keystone-gateway/state.key
```

### 5\. Configure the Gateway

The archive installs a default configuration at `/opt/particle_horizon/local_config/keystone-gateway/gateway.yaml`. Edit it in place for the environment, replacing `gw-acme-production`, `gateway-a`, and the address with your values. A minimal standalone configuration looks like this:

```
gateway_id: gw-acme-production
api:
  http:
    enabled: true
    listen: "127.0.0.1:8080"
    advertise_url: "http://gateway-a.internal:8080"
    allow_insecure_http: true
  auth:
    mode: none
    principal_id: local-operator
    allowed_license_type: phocol
    operator: true
storage:
  directory: /opt/particle_horizon/local_data/keystone-gateway/state
  encryption_key_file: /opt/particle_horizon/local_config/keystone-gateway/state.key
license: { file: /opt/particle_horizon/local_config/keystone-gateway/gateway-license.json }
ha: { enabled: false, replica_id: gateway-a, bootstrap_expect: 1, peer_loss_timeout_seconds: 5 }
leases: { default_ttl_seconds: 120, min_ttl_seconds: 30, max_ttl_seconds: 300, max_clock_skew_seconds: 5, expiry_sweep_seconds: 15 }
reports: { directory: /opt/particle_horizon/local_data/keystone-gateway/reports, retention_days: 90 }
logging: { level: info, format: json }
```

This minimal `none`\-mode example creates one shared principal authorized only for PHOCOL leases. It cannot also authorize LMC Exporter leases. To distinguish installations or serve both product types, replace the fixed `api.auth` fields with the mTLS or bearer multi-principal setup in [configuration examples](https://portal.cjcit.io/knowledge-base/keystone_gateway/0.2/configuration/examples/#multiple-caller-principals).

### 6\. Install the license and validate

Retrieve the identity as described in [licensing](https://portal.cjcit.io/knowledge-base/keystone_gateway/0.2/getting-started/licensing/), obtain the license, and install it mode `0600` owned by the service account. Validate the resolved configuration:

```
sudo /opt/particle_horizon/keystone-gateway config validate \
  --config /opt/particle_horizon/local_config/keystone-gateway/gateway.yaml
```

Expected result: `configuration is valid: /opt/particle_horizon/local_config/keystone-gateway/gateway.yaml`.

### 7\. Install the systemd service

Create `/etc/systemd/system/keystone-gateway.service`:

```
sudo tee /etc/systemd/system/keystone-gateway.service >/dev/null <<'EOF'
[Unit]
Description=Particle Horizon Keystone Gateway
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/opt/particle_horizon/keystone-gateway serve --config /opt/particle_horizon/local_config/keystone-gateway/gateway.yaml
Restart=on-failure
Type=simple
User=keystone-gateway
Group=keystone-gateway
WorkingDirectory=/opt/particle_horizon

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

Reload systemd, then start and enable the service:

```
sudo systemctl daemon-reload
sudo systemctl enable --now keystone-gateway
```

### 8\. Verify the service

```
sudo systemctl status keystone-gateway
curl --fail http://127.0.0.1:8080/healthz
curl --fail http://127.0.0.1:8080/readyz
```

Both probes return JSON status values (`ok`, then `ready`). If readiness fails, use [troubleshooting](https://portal.cjcit.io/knowledge-base/keystone_gateway/0.2/troubleshooting/); do not route client traffic on `/healthz` alone.

Confirm which release is active with:

```
readlink -f /opt/particle_horizon/keystone-gateway
```

## Rollback

Stop the service and restore the prior release, configuration, license, state key, and state directory from a tested backup as a matching set. Because the archive keeps each executable beneath a version-specific `SOFTWARE` directory, a previously installed version can be restored by repointing the stable path at it:

```
sudo systemctl stop keystone-gateway
sudo ln -sfn \
  SOFTWARE/keystone-gateway-<previous-version>-linux-<arch>/bin/keystone-gateway \
  /opt/particle_horizon/keystone-gateway
sudo systemctl start keystone-gateway
```

Do not restore encrypted state with a different key.

## File locations

<table><caption class="sr-only">File locations</caption><thead><tr><th scope="col">Purpose</th><th scope="col">Location</th></tr></thead><tbody><tr><td>Stable executable</td><td><code>/opt/particle_horizon/keystone-gateway</code></td></tr><tr><td>Versioned software</td><td><code>/opt/particle_horizon/SOFTWARE/</code></td></tr><tr><td>Gateway configuration</td><td><code>/opt/particle_horizon/local_config/keystone-gateway/gateway.yaml</code></td></tr><tr><td>Signed license</td><td><code>/opt/particle_horizon/local_config/keystone-gateway/gateway-license.json</code></td></tr><tr><td>State-encryption key</td><td><code>/opt/particle_horizon/local_config/keystone-gateway/state.key</code></td></tr><tr><td>Durable state</td><td><code>/opt/particle_horizon/local_data/keystone-gateway/state/</code></td></tr><tr><td>Reports</td><td><code>/opt/particle_horizon/local_data/keystone-gateway/reports/</code></td></tr><tr><td>Gateway logs</td><td><code>/opt/particle_horizon/logs/keystone-gateway/</code></td></tr><tr><td>systemd unit</td><td><code>/etc/systemd/system/keystone-gateway.service</code></td></tr></tbody></table>
