> For the complete documentation index, see [llms.txt](https://docs.k9.io/key9-identity/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.k9.io/key9-identity/jsonair/2-install/2.6-agent-configuration.md).

# 2.6 Configuring and Running the JSONAir Agent

The JSONAir agent (`jsonair-agent`) is a lightweight process that runs on the target host. It periodically polls the JSONAir server for configuration updates, writes the result to a local file, and executes a reload command when the configuration changes.

Like the server, the agent is configured entirely through environment variables or a `.env` file placed in the same directory as the binary.

***

## How It Works

1. The agent authenticates with the JSONAir server using its PAT and receives a short-lived JWT.
2. On each polling interval, it fetches the configuration for the configured `type` and `name`.
3. It computes a SHA-256 hash of the retrieved data and compares it to the hash of the file currently on disk.
4. If the hashes match, nothing has changed — the agent sleeps and tries again next interval.
5. If the hashes differ, the agent backs up the existing file, writes the new configuration to disk, and executes the `RELOAD_COMMAND`.

***

## Resilience — Surviving a Server Outage

The agent is designed to keep running even if the JSONAir server is temporarily unreachable. It will **never exit** due to a network error or a bad HTTP response from the server. Instead, it logs the error, sleeps for the configured `SLEEP` interval, and retries on the next cycle.

| Condition                                           | Agent Behaviour                                     |
| --------------------------------------------------- | --------------------------------------------------- |
| Server unreachable (connection refused, timeout)    | Logs the error, sleeps, retries                     |
| Server returns an unexpected HTTP status (e.g. 500) | Logs the status, sleeps, retries                    |
| Server returns 401 (JWT expired)                    | Re-authenticates using the PAT, retries immediately |
| Server returns 200                                  | Processes the configuration normally                |

This means the last successfully written configuration file remains in place on disk and the consuming application continues running uninterrupted while the JSONAir server is down.

***

## Environment Variables

### Required — JSONAir Server

| Variable       | Description                                                           | Example                       |
| -------------- | --------------------------------------------------------------------- | ----------------------------- |
| `JSONAIR_PAT`  | Plain-text Personal Access Token used to authenticate with the server | `your-secret-pat`             |
| `JSONAIR_URL`  | Base URL of the JSONAir server (no trailing slash)                    | `https://jsonair.example.com` |
| `JSONAIR_TYPE` | Configuration type to retrieve                                        | `suricata`                    |
| `JSONAIR_NAME` | Configuration name to retrieve                                        | `suricata.yaml`               |

### Required — Local File Management

| Variable         | Description                                                                                                                            | Example                        |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ |
| `CONFIG_FILE`    | Full path where the configuration file will be written                                                                                 | `/etc/suricata/suricata.yaml`  |
| `RELOAD_COMMAND` | Command to execute after a configuration update is written to disk. Runs with the arguments split on whitespace — no shell is invoked. | `/usr/bin/killall -1 suricata` |
| `PRUNE`          | Number of backup files to keep. Older backups are deleted automatically.                                                               | `5`                            |
| `SLEEP`          | Polling interval in seconds                                                                                                            | `300`                          |

### Optional — File Permissions

| Variable | Description                                                   | Default |
| -------- | ------------------------------------------------------------- | ------- |
| `MASK`   | Octal file permission mask for the written configuration file | `0600`  |

### Required — Process

| Variable | Description                                  | Example  |
| -------- | -------------------------------------------- | -------- |
| `RUNAS`  | Username to drop privileges to after startup | `nobody` |

***

## The RELOAD\_COMMAND

The `RELOAD_COMMAND` is executed every time the agent detects a configuration change and writes a new file to disk. This allows the agent to signal whatever service consumes the configuration to reload it — without requiring a restart.

The command is split on whitespace and executed directly (no shell). This prevents shell injection. Use full paths to binaries.

**Common examples:**

| Use case                 | RELOAD\_COMMAND                   |
| ------------------------ | --------------------------------- |
| Send SIGHUP to a process | `/usr/bin/killall -1 suricata`    |
| Reload via systemctl     | `/usr/bin/systemctl reload nginx` |
| Run a custom script      | `/usr/local/bin/reload-app.sh`    |

> **Note:** The reload command runs as the user specified by `RUNAS`. Make sure that user has permission to execute the command.

***

## Example `.env` File

```ini
# JSONAir Server
JSONAIR_PAT=your-secret-pat
JSONAIR_URL=https://jsonair.example.com
JSONAIR_TYPE=suricata
JSONAIR_NAME=suricata.yaml

# Local file management
CONFIG_FILE=/etc/suricata/suricata.yaml
RELOAD_COMMAND=/usr/bin/killall -1 suricata
PRUNE=5
SLEEP=300
MASK=0644

# Process
RUNAS=nobody
```

***

## Starting the Agent

### Directly

```bash
cd /opt/jsonair-agent
./jsonair-agent
```

### Using export.sh

A convenience script is included at `cmd/jsonair-agent/scripts/export.sh`. Copy and edit it with your values:

```bash
cp cmd/jsonair-agent/scripts/export.sh /opt/jsonair-agent/start.sh
# Edit start.sh with your values
chmod +x /opt/jsonair-agent/start.sh
./start.sh
```

### As a systemd Service

Create `/etc/systemd/system/jsonair-agent.service`:

```ini
[Unit]
Description=JSONAir Agent
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/opt/jsonair-agent
EnvironmentFile=/etc/jsonair/agent.env
ExecStart=/opt/jsonair-agent/jsonair-agent
Restart=on-failure
RestartSec=10
KillMode=mixed
TimeoutStopSec=30

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

Place your environment variables in `/etc/jsonair/agent.env`, then enable and start the service:

```bash
sudo systemctl daemon-reload
sudo systemctl enable jsonair-agent
sudo systemctl start jsonair-agent
sudo systemctl status jsonair-agent
```

***

## Backup Behavior

Each time a configuration change is detected, the agent creates a timestamped backup of the existing file before overwriting it:

```
/etc/suricata/suricata.yaml.20260422-143022-123456789.bak
```

The `PRUNE` variable controls how many backups are retained. Once the limit is exceeded, the oldest backup is automatically deleted.
