Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ad2abd7
docs: add detailed agent subsystem documentation
rodneyosodo Jun 10, 2026
f205162
fix(senml): use seconds for SenML base time
rodneyosodo Jun 11, 2026
55d2afd
docs(readme): add config store, telemetry, reset modes, heartbeat
rodneyosodo Jun 11, 2026
5e53575
build(makefile): use v0.0.0 as default VERSION
rodneyosodo Jun 11, 2026
a41736f
docs(heartbeat): use plain MQTT port 1883 and update samples
rodneyosodo Jun 11, 2026
cd5b336
fix(agent): start telemetry once and allow zero interval
rodneyosodo Jun 11, 2026
eb30396
docs(telemetry): simplify examples, drop arch diagram and table
rodneyosodo Jun 11, 2026
5f52841
feat(service): return running config when key missing
rodneyosodo Jun 11, 2026
263f15e
docs(control): use port 1883, add response examples, remove arch
rodneyosodo Jun 11, 2026
96abbaa
docs(bootstrap): update examples and sample output
rodneyosodo Jun 12, 2026
4dc6b37
fix(terminal): use $SHELL with /bin/sh fallback
rodneyosodo Jun 12, 2026
9c843b1
build(docker): use alpine:3.23.4 base and include certs
rodneyosodo Jun 12, 2026
17d45bd
refactor: buffer ptmx reads, add structured logs, rename char flag
rodneyosodo Jun 12, 2026
16687ad
docs(terminal): switch examples to port 1883 and update UI URL
rodneyosodo Jun 12, 2026
77fc343
fix(service): set 30s timeout for device provisioning
rodneyosodo Jun 12, 2026
db85c27
docs(devices): clarify MQTT creds, add rollback and testing
rodneyosodo Jun 12, 2026
1686912
feat(conn): publish NodeRed responses and errors
rodneyosodo Jun 12, 2026
10879a8
docs(health): update health page examples and metrics output
rodneyosodo Jun 12, 2026
a5c8c85
docs(nodered): set Agent UI port to 9999 and update examples
rodneyosodo Jun 12, 2026
4203fdb
docs(ota): use port 1883 and remove TLS options in examples
rodneyosodo Jun 12, 2026
807f032
chore(docker): Add agent service URLs and PAT to env
rodneyosodo Jun 12, 2026
8cb3edf
test(terminal): check sess.Send error and fix indentation
rodneyosodo Jun 12, 2026
0dc7ee8
docs(readme): simplify README, update quickstart and ports
rodneyosodo Jun 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES))
CGO_ENABLED ?= 0
GOARCH ?= amd64
DOCKER_IMAGE_NAME_PREFIX ?= ghcr.io/absmach
VERSION ?= $(shell git describe --abbrev=0 --tags 2>/dev/null || echo 'unknown')
VERSION ?= $(shell git describe --abbrev=0 --tags 2>/dev/null || echo 'v0.0.0')
COMMIT ?= $(shell git rev-parse HEAD)
TIME ?= $(shell date +%F_%T)
MOCKERY = $(GOBIN)/mockery
Expand Down
347 changes: 52 additions & 295 deletions README.md

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions docker/.env
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,8 @@ MG_AGENT_DEVICE_DB_PATH=/var/lib/agent/devices.db

## Config Store
MG_AGENT_CONFIG_PATH=/var/lib/agent/agent-config.json

MG_AGENT_CLIENTS_URL="http://host.docker.internal:9006"
MG_AGENT_CHANNELS_URL="http://host.docker.internal:9005"
MG_AGENT_RULES_ENGINE_URL="http://host.docker.internal:9008"
MG_PAT=""
3 changes: 1 addition & 2 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ RUN apk update && apk add make && \
-o build/magistrala-$SVC cmd/main.go && \
mv build/magistrala-$SVC /exe

FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
FROM alpine:3.24
COPY --from=builder /exe /
ENTRYPOINT ["/exe"]
4 changes: 4 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ services:
MG_AGENT_OTA_DOWNLOAD_DIR: ${MG_AGENT_OTA_DOWNLOAD_DIR}
MG_AGENT_DEVICE_DB_PATH: ${MG_AGENT_DEVICE_DB_PATH}
MG_AGENT_CONFIG_PATH: ${MG_AGENT_CONFIG_PATH}
MG_AGENT_CLIENTS_URL: ${MG_AGENT_CLIENTS_URL}
MG_AGENT_CHANNELS_URL: ${MG_AGENT_CHANNELS_URL}
MG_AGENT_RULES_ENGINE_URL: ${MG_AGENT_RULES_ENGINE_URL}
MG_PAT: ${MG_PAT}
volumes:
- /etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt:ro
- agent-data:/var/lib/agent
16 changes: 15 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
Agent docs placeholder
# Magistrala Agent Documentation

Per-feature documentation with configuration, MQTT topic maps, and copy-paste test recipes.

| Document | Description |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------- |
| [heartbeat.md](heartbeat.md) | Self-heartbeat and service liveness tracking, interval configuration, test recipes |
| [telemetry.md](telemetry.md) | Periodic uptime telemetry, payload format, runtime configuration, test recipes |
| [control.md](control.md) | Command dispatch, runtime config get/set/reset, token authentication, exec subsystem, test recipes |
| [bootstrap.md](bootstrap.md) | Profile-based provisioning flow, environment variables, cache management, test recipes |
| [ota.md](ota.md) | Over-the-air binary updates, trigger payload, download/verify/replace cycle, status reporting, test recipes |
| [terminal.md](terminal.md) | Interactive terminal sessions over MQTT, session lifecycle, PTY management, test recipes |
| [nodered.md](nodered.md) | Node-RED integration, flow deployment, provisioning, HTTP and MQTT management, test recipes |
| [devices.md](devices.md) | Downstream device provisioning, physical interfaces, device CRUD, telemetry scheduler, test recipes |
| [health.md](health.md) | Health supervisor, systemd watchdog integration, MQTT connection monitoring, health check endpoints |
191 changes: 191 additions & 0 deletions docs/bootstrap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
# Bootstrap

The bootstrap subsystem handles profile-based provisioning. At startup, the agent fetches a rendered bootstrap profile from the Magistrala Bootstrap service, which provides device identity, MQTT credentials, channel IDs, and provision configuration. The profile is cached locally so subsequent starts skip the HTTP fetch.

## Bootstrap Flow

1. **Agent starts** with `MG_AGENT_BOOTSTRAP_URL`, `MG_AGENT_BOOTSTRAP_EXTERNAL_ID`, and `MG_AGENT_BOOTSTRAP_EXTERNAL_KEY` set.
2. **Check cache** — if bootstrap-derived fields are already in the persistent config store and `bs_valid` is `1`, skip the HTTP fetch.
3. **Fetch profile** — HTTP GET to the bootstrap endpoint with the external ID and key.
4. **Parse content** — the bootstrap response wraps a JSON string in `content`:
```json
{
"content": "{ \"device_id\": \"...\", \"mqtt\": { ... } }",
"client_key": "",
"client_cert": "",
"ca_cert": ""
}
```
5. **Merge with env config** — bootstrap fields override env defaults for `domain_id`, `channels`, and `mqtt`.
6. **Persist** — bootstrap fields are saved to the persistent config store (`agent-config.json`).
7. **Load certificates** — if mTLS is configured, client and CA certs are loaded.
8. **Connect MQTT** — the agent connects using the bootstrap-provided credentials.

## Rendered Profile Content

The `content` field from the bootstrap response decodes to:

```json
{
"device_id": "<client-id>",
"external_id": "<external-id>",
"domain_id": "<domain-id>",
"mqtt": {
"url": "ssl://host.docker.internal:8883",
"client_id": "<client-id>",
"secret": "<client-secret>"
},
"telemetry": {
"channel_id": "<telemetry-channel-id>",
"topic": "m/<domain-id>/c/<telemetry-channel-id>/msg"
},
"commands": {
"channel_id": "<commands-channel-id>"
}
}
```

## Configuration

### Environment Variables

| Variable | Default | Description |
| ---------------------------------------- | ------------------------------- | --------------------------------------------------------------------------- |
| `MG_AGENT_BOOTSTRAP_URL` | `""` | Bootstrap service base URL (e.g. `http://bootstrap:9013/clients/bootstrap`) |
| `MG_AGENT_BOOTSTRAP_EXTERNAL_ID` | `""` | Device external ID used to look up the profile |
| `MG_AGENT_BOOTSTRAP_EXTERNAL_KEY` | `""` | Device external key (sent as `Authorization: Client <key>`) |
| `MG_AGENT_BOOTSTRAP_RETRIES` | `5` | Number of retries when fetching bootstrap profile |
| `MG_AGENT_BOOTSTRAP_RETRY_DELAY_SECONDS` | `10` | Delay between retries in seconds |
| `MG_AGENT_BOOTSTRAP_SKIP_TLS` | `false` | Skip TLS verification for bootstrap HTTP fetch |
| `MG_AGENT_BOOTSTRAP_CACHE_PATH` | `/var/lib/agent/bootstrap.json` | Local file path for caching the bootstrap response |

### When Bootstrap Is Active

Bootstrap mode activates when **all three** are set:

- `MG_AGENT_BOOTSTRAP_URL`
- `MG_AGENT_BOOTSTRAP_EXTERNAL_ID`
- `MG_AGENT_BOOTSTRAP_EXTERNAL_KEY`

When bootstrap is active, the legacy `config.toml` file is ignored.

### Persistent Config Store

| Variable | Default | Description |
| ---------------------- | ------------------- | ---------------------------------------------------------- |
| `MG_AGENT_CONFIG_PATH` | `agent-config.json` | Path to the JSON file used for persistent config overrides |

Bootstrap-derived fields persisted in the store:

| Key | Source |
| ------------------ | ----------------------------------- |
| `domain_id` | Bootstrap profile |
| `channels_ctrl_id` | Bootstrap profile |
| `channels_data_id` | Bootstrap profile |
| `mqtt_url` | Bootstrap profile |
| `mqtt_username` | Bootstrap profile |
| `mqtt_password` | Bootstrap profile |
| `bs_valid` | Set to `"1"` after successful fetch |

## Provisioning

### Automated (recommended)

```bash
export MG_AGENT_BOOTSTRAP_EXTERNAL_ID="<device-external-id>"
export MG_AGENT_BOOTSTRAP_EXTERNAL_KEY="<device-external-key>"
export MG_DOMAIN_ID="<domain-id>"
export MG_PAT="<personal-access-token>"
make run_provision
```

The provisioning script creates:

1. A Client (device) with credentials
2. Telemetry and commands Channels
3. A Bootstrap Profile and Enrollment with `external_id` and `external_key`
4. Profile bindings to the provisioned client and channels
5. A Rule Engine rule with `save_senml` output for telemetry

### Cloud provisioning

```bash
export MG_API="https://cloud.magistrala.absmach.eu/api"
export MG_AGENT_MQTT_URL=ssl://messaging.magistrala.absmach.eu:8883
export MG_AGENT_MQTT_SKIP_TLS=false
export MG_AGENT_BOOTSTRAP_EXTERNAL_ID="<device-external-id>"
export MG_AGENT_BOOTSTRAP_EXTERNAL_KEY="<device-external-key>"
export MG_DOMAIN_ID="<domain-id>"
export MG_PAT="<pat>"
make run_provision
```

## Test Recipes

### Fetch the bootstrap profile manually

```bash
curl -s 'http://localhost:9013/clients/bootstrap/<external-id>' \
-H 'accept: */*' \
-H 'Authorization: Client <external-key>'
```

**Expected response:**

```json
{
"id": "fa846d56-3100-44aa-8385-3a88cb437a5a",
"content": "{\n \"commands\": {\n \"channel_id\": \"bc9a0af7-6d0f-4806-aa5a-61d68c0a7cf7\"\n },\n \"device_id\": \"fa846d56-3100-44aa-8385-3a88cb437a5a\",\n \"domain_id\": \"e9692c28-b730-4797-8a15-2e25c08f9641\",\n \"external_id\": \"019eb690777d7452ba898a66f5cc9cb8\",\n \"mqtt\": {\n \"client_id\": \"ffec2491-0de1-4051-9e75-ad2e2d241627\",\n \"secret\": \"30c775d7-3504-42c6-976c-52c02474bf2f\",\n \"url\": \"ssl://host.docker.internal:8883\"\n },\n \"provision\": {\n \"channels_url\": \"http://channels:9005\",\n \"clients_url\": \"http://clients:9006\",\n \"rules_engine_url\": \"http://rules:9008\",\n \"token\": \"pat_TurQa8bRR72vtZguCtIIe8ZTeaSkqkinkhLxSqPo7bw=_PoOG@UuEadfD!F7TcWYzsDKSxLB%3mzlh1M\\u0026MmLIky0M8A2Ui9f9J^4DuzZ@O0rjCA-cvgjbuFjOofOwreHL-j\\u0026CcgffH7FzwoDC\"\n },\n \"telemetry\": {\n \"channel_id\": \"b465a688-c1ca-417d-a36f-71f6f1be2409\",\n \"topic\": \"\\u003cno value\\u003e\"\n }\n}"
}
```

### Force a bootstrap re-fetch at runtime

```bash
mosquitto_pub \
-h <mqtt-host> -p 1883 \
-u <client-id> -P <client-secret> --id "cfg-$(date +%s)" \
-t "m/<domain-id>/c/<commands-channel-id>/req" \
-m '[{"bn":"req-1:", "n":"config", "vs":"set,bs_valid,0"}]'
```

This sets `bs_valid` to `0` and deletes the cached bootstrap profile. On the **next restart**, the agent will re-fetch the profile from the bootstrap service.

### Check bootstrap cache status

```bash
mosquitto_pub \
-h <mqtt-host> -p 1883 \
-u <client-id> -P <client-secret> --id "cfg-$(date +%s)" \
-t "m/<domain-id>/c/<commands-channel-id>/req" \
-m '[{"bn":"req-1:", "n":"config", "vs":"get,bs_valid"}]'
```

**Response:** `1` (valid cache) or `0` (cache invalidated).

### Run agent without Docker

```bash
export MG_AGENT_BOOTSTRAP_EXTERNAL_ID="<external-id>"
export MG_AGENT_BOOTSTRAP_EXTERNAL_KEY="<external-key>"
export MG_AGENT_BOOTSTRAP_URL="http://localhost:9013/clients/bootstrap"
build/magistrala-agent
```

### Verify agent startup logs

After a successful bootstrap fetch, the agent logs:

```json
{"level":"INFO","msg":"Client connected","client_name":"<client-id>"}
{"level":"INFO","msg":"Agent service started","port":"9999"}
```

If bootstrap data is already cached:

```json
{
"level": "INFO",
"msg": "Bootstrap data already present, skipping bootstrap fetch"
}
```
Loading
Loading