> ## Documentation Index
> Fetch the complete documentation index at: https://docs.novacula.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Provision an executor on bare-metal

> Install the Novacula Agent as a systemd service on a Linux host and bring it online

This guide takes you from a fresh Linux host to a registered, online executor. End-state: the host runs `nvcl-agent.service`, the Hub sees it as `online`, and you can deploy nodes onto it from the UI.

The **Agent** is the bare-metal / VM executor: it runs as a `systemd` service and manages each node's processes as their own `systemd` units. Choose it when your nodes live on hosts or VMs you already manage and you don't want a Kubernetes dependency. For the executor concept and the Agent-vs-Operator choice, see [Executors](/docs/executors/overview).

## Prerequisites

* **OS** — a `linux/x86_64` host with `systemd`.
* **Privileges** — `root` or `sudo`.
* **Network** — outbound HTTPS to the Hub (`https://hub.novacula.io`). No inbound ports are needed.
* **API key** — see [Connect an executor](/docs/executors/connect-an-executor) to issue one. You'll need the `exc_`-prefixed key in the next step.

Java is **not** required — the Agent ships as a self-contained image with its own bundled JRE.

## Steps

<Steps>
  <Step title="Run the installer">
    The production way to bring a host online is the Hub's one-line installer: the Hub serves a script that downloads the Agent, writes its config, and starts it as a `systemd` service — the host then syncs to the Hub and reconciles whatever nodes it's assigned. From the **Connect Executor** screen in the UI, copy the command (the Hub renders it for your organization, with your Hub URL already baked in). It looks like this:

    ```bash theme={null}
    curl -fsSL https://hub.novacula.io/install.sh | sudo \
      NVCL_TOKEN="<executor-api-key>" \
      NVCL_NAME="my-executor" sh
    ```

    The script — served by the Hub and rendered from its latest active Agent release — downloads the self-contained Agent image, verifies its SHA-256 checksum, extracts it to `/opt/nvcl/agent`, writes `/etc/nvcl/agent.toml` (mode `0600`, with your Hub URL and API key baked in), registers `nvcl-agent.service`, and starts it. If no active Agent release is published yet, `/install.sh` returns a 404.

    | Variable            | Required | Description                                                                                                                                     |
    | ------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
    | `NVCL_TOKEN`        | yes      | Executor API key (`exc_…`). Baked into `agent.toml` as `hub.token`.                                                                             |
    | `NVCL_NAME`         | no       | Executor identity in the Hub (unique per org). Defaults to the host's hostname.                                                                 |
    | `NVCL_PREFIX`       | no       | Stage `/opt/nvcl` and `/etc/nvcl` under this prefix instead of `/` (inspect the layout without touching the system).                            |
    | `NVCL_SKIP_SYSTEMD` | no       | Set to `1` to skip the OS/arch gate and the systemd unit (e.g. containers). Combine with `NVCL_PREFIX=<user-writable dir>` for a root-free run. |

    <Warning>
      The installer **aborts if an agent is already installed** (an existing `/opt/nvcl/agent` binary or `/etc/nvcl/agent.toml`), so a re-run of `curl | sh` can't silently overwrite a working install. To intentionally reinstall, set `NVCL_FORCE=1`:

      ```bash theme={null}
      curl -fsSL https://hub.novacula.io/install.sh | sudo \
        NVCL_TOKEN="<executor-api-key>" NVCL_NAME="my-executor" NVCL_FORCE=1 sh
      ```

      Moving an existing executor to a newer version does **not** use `NVCL_FORCE` — [upgrade it from the UI](/docs/executors/upgrade-executor) instead (the Hub drives self-update).
    </Warning>
  </Step>

  <Step title="Confirm online in the UI">
    From **Executors**, your new executor row should be `online`, with capabilities populated for every chain it supports.

    The executor reports in within a few seconds and the row turns `online`. If it stays `offline`:

    * Check `journalctl -u nvcl-agent.service -n 100` for auth or network errors.
    * Confirm outbound HTTPS to the Hub URL.
    * Verify the API key was copied without trailing whitespace.
  </Step>
</Steps>

## What the installer does

Under the hood, the one-line command:

1. **Downloads** the self-contained Agent image tarball from the Hub's latest active release and verifies its SHA-256 checksum.
2. **Extracts** it to `/opt/nvcl/agent` (launcher `bin/agent`, jars under `lib/`, and a bundled JRE under `runtime/` — no host Java needed).
3. **Writes** `/etc/nvcl/agent.toml` (mode `0600`) with `[source] type = "hub"`, the Hub URL (`hub.url`), your API key (`hub.token`), and the executor name.
4. **Registers and starts** `nvcl-agent.service` (start on boot, restart on failure).

## What the Agent owns on disk

After install:

```
/etc/nvcl/agent.toml                     ← config (written by installer, mode 0600)
/etc/systemd/system/nvcl-agent.service   ← the agent daemon unit
/opt/nvcl/agent/                         ← the agent image (bin/agent, lib/, bundled JRE)
/var/lib/nvcl/                           ← data_dir (see below)
```

The data directory (`[agent] data_dir = "/var/lib/nvcl"`) holds everything the agent manages:

```
/var/lib/nvcl/
├── agent.spec.json             ← desired state (in hub mode, written from the last sync)
├── agent.state.json            ← observed state (written every tick)
├── ports.json                  ← host port ledger
├── clients/<client>-<version>/ ← downloaded, SHA-verified chain client binaries
├── proxy/caddy-<version>/      ← the Caddy proxy binary
├── caddy/                      ← Caddy data: ACME account, issued certs
├── rpc-keys/<keyId>.json       ← per-node RPC keys (0600; sha256 hash only)
└── nodes/<id>/<role>/          ← per-node chain data + generated config
```

Each node's processes run as their own `systemd` units named `nvcl-node-<id>-<role>.service`. **Node data is preserved:** removing a node stops and deletes its units and releases its ports, but `nodes/<id>/` stays on disk — delete it manually to reclaim space.

## Managing the agent on the host

The installer runs the agent for you; you never start the daemon by hand. Manage it with `systemctl`, and read what it observes with the `agent` CLI:

```bash theme={null}
systemctl status nvcl-agent.service                          # daemon status
journalctl -u nvcl-agent.service -f                          # follow logs
sudo systemctl restart nvcl-agent.service                    # restart (e.g. after a config edit)
/opt/nvcl/agent/bin/agent get --config /etc/nvcl/agent.toml  # the per-tick node state it observes
```

Stopping the daemon (`sudo systemctl stop nvcl-agent.service`) does **not** stop the nodes — their `nvcl-node-<id>-<role>.service` units keep running.

**Uninstall / wipe the host:**

```bash theme={null}
sudo systemctl disable --now nvcl-agent.service 'nvcl-node-*.service'
sudo rm -f /etc/systemd/system/nvcl-agent.service /etc/systemd/system/nvcl-node-*.service
sudo systemctl daemon-reload
sudo rm -rf /etc/nvcl /var/lib/nvcl /opt/nvcl
```

**Upgrades** are driven from the Hub — a dispatched release rides the sync response down and the agent self-updates in place (downloads the new image, verifies its sha256, swaps `/opt/nvcl/agent` keeping one `.prev`, and restarts). See [Upgrade an executor](/docs/executors/upgrade-executor).

## Configuration (`agent.toml`)

The installer writes `/etc/nvcl/agent.toml` (mode `0600`) for you; edit it afterward to tune behaviour, then `sudo systemctl restart nvcl-agent.service` (the config is read once at startup). A hub-driven config looks like:

```toml theme={null}
[source]
type = "hub"                     # desired state comes from the Hub each tick

[agent]
data_dir = "/var/lib/nvcl"       # working root: spec/state, ports, client binaries, node data

[hub]
url = "https://hub.novacula.io"  # Hub URL (no trailing slash)
token = "exc_..."                # your executor API key
name = "my-host"                 # executor identity in the Hub (default: hostname)

[reconciler]
interval_ms = 10000              # how often desired state is reconciled

[logging]
level = "info"                   # trace | debug | info | warn | error
format = "pretty"                # pretty | json
```

* `[source] type` is the one mandatory key; in hub mode `[hub] url` and `token` are required (the agent won't sync without them).
* Parsing is **strict** — an unknown key or section is a startup error.
* In hub mode, telemetry is routed through the Hub automatically; you don't configure a collector here.

Per-node settings (resources, config-file and CLI-argument overrides, RPC exposure) aren't in `agent.toml` — you set them per node in the UI. See [Edit node configuration](/docs/nodes/edit-configuration).

## Hardening

* **Keep `agent.toml` mode `0600`**, owned by `root`. The API key (`hub.token`) is the credential.
* **Let the Hub drive upgrades** — self-update verifies the sha256 and keeps a `.prev` for rollback.
* **Restrict outbound** to the Hub URL plus the per-chain client download mirrors if you want strict egress.

## Next steps

* [Deploy a node](/docs/nodes/deploy-a-node) onto your fresh executor.
