# Local Installer Agent

FastAPI service that runs on each Linux client and listens on `127.0.0.1:5010`.

It accepts install, update, remove, task, log, installed-app, and service-control requests from `robot.installer`. It stores state in local SQLite and installs trusted `.deb` components downloaded from `robot.package`, Ubuntu APT packages, plus Docker image components from allowed registries when Docker support is enabled.

## Development

```bash
cd agent
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --host 127.0.0.1 --port 5010
```

## Install script URL

Client machines should keep using the stable installer command:

```bash
curl -fsSL https://robot.package/install-agent.sh | sudo bash
```

The web server resolves `https://robot.package/packages/agent/latest.deb?arch=<dpkg-arch>`
to the newest uploaded `local-installer-agent_<version>_<arch>.deb`, so updating the Agent does not require editing the install command.

Agent packages are uploaded from the web server Admin page at `/agent`. The default storage folder is
`web-server/uploads/packages/agent`, and it can be changed with `AGENT_PACKAGE_DIR`.

## Important API

```text
GET  /health
GET  /system-info
GET  /apps/installed
POST /apps/install
POST /apps/update
POST /apps/remove
GET  /tasks/{taskId}
GET  /tasks/{taskId}/logs
GET  /tasks/{taskId}/components
POST /services/start
POST /services/stop
POST /services/restart
GET  /services/{serviceName}/status
```

`POST /apps/install` supports both:

```json
{
  "appId": "robot-suite",
  "version": "1.0.0"
}
```

and a direct single `.deb` payload:

```json
{
  "appId": "robot-web-app",
  "appName": "Robot Web App",
  "packageName": "robot-web-app",
  "serviceName": "robot-web-app.service",
  "version": "1.0.0",
  "downloadUrl": "https://robot.package/packages/robot-web-app_1.0.0_amd64.deb",
  "checksum": "sha256_hash_here"
}
```

For manifest mode, the Agent fetches:

```text
{ROBOT_PACKAGE_BASE_URL}/api/apps/{appId}/versions/{version}/manifest
```

Docker image components require Docker Engine on the client machine. By default `AUTO_INSTALL_DOCKER=true`, so the agent will install the trusted distro package `docker.io` with `apt-get` when a Docker app is installed and Docker is missing. Set `AUTO_INSTALL_DOCKER=false` if Docker must be provisioned by your own fleet policy. The agent validates `image` against `ALLOWED_DOCKER_REGISTRIES`, then runs a managed container using the manifest fields `containerName`, `restartPolicy`, `ports`, `volumes`, and `env`.

APT components use a fixed manifest contract and never accept shell commands:

```json
{
  "componentId": "postgresql",
  "type": "apt",
  "packageName": "postgresql",
  "version": "16"
}
```

`ALLOWED_APT_PACKAGES=*` allows every syntactically valid APT package name and is the default for newly built Agents. This lets an administrator enter an exact package code on the Web Server without updating each Agent's configuration for every new package.

To enable this behavior on an already deployed Agent:

```bash
sudo sed -i \
  's/^ALLOWED_APT_PACKAGES=.*/ALLOWED_APT_PACKAGES=*/' \
  /etc/local-installer-agent/agent.env
sudo systemctl restart local-installer-agent
```

To restore a restricted allowlist, use a comma-separated list without spaces:

```bash
ALLOWED_APT_PACKAGES=postgresql,nginx,redis-server,curl
```

Every allowed package must be available from the APT sources configured on the target client. The Agent runs `apt-get update`, but it does not add third-party repositories or signing keys.

APT packages can execute maintainer scripts as root during installation. Wildcard mode should therefore only be used when the Web Server and package-upload accounts are trusted and access-controlled.

After installation, the Agent discovers concrete systemd `.service` units shipped by the trusted package and reads their state with a fixed `systemctl show` argv (no shell command is accepted from the manifest or Web Client). The structured result is returned as `serviceCheckStatus` and `serviceChecks` from the task component API and is retained with the installed app for the UI.

PostgreSQL keeps an additional trusted policy: the Agent enables and starts `postgresql.service`, verifies that it is active, and waits for `pg_isready`. Other discovered package services are checked read-only; they are not blindly enabled or started because packages can ship optional or one-shot units.

`GET /apps/installed` refreshes the current systemd state for retained service names. For the trusted PostgreSQL policy it also re-runs `pg_isready`, so the UI does not report a healthy meta-service while the database is unavailable.

Example task component service result:

```json
{
  "serviceCheckStatus": "healthy",
  "serviceChecks": [
    {
      "serviceName": "postgresql.service",
      "loadState": "loaded",
      "activeState": "active",
      "subState": "running",
      "unitFileState": "enabled",
      "healthy": true,
      "readinessStatus": "ready",
      "checkedAt": "2026-07-20T00:00:00Z"
    }
  ]
}
```
