Перейти до основного вмісту

🤖 station-agent

Native Node.js binary running on the Raspberry Pi as a systemd service. Manages the Docker stack, ESP32 firmware OTA, and Wi-Fi networking (including a captive portal for first-boot setup).

Source ↗ — built as a SEA (Single Executable Application), no Node.js needed on the host.

Responsibilities

  • 🐳 Docker updates — poll release.json, pull new images, healthcheck, auto-rollback on failure
  • 📡 Firmware OTA — poll firmware/manifest.json, download .bin files, hand them off to the backend → MQTT → ESP32
  • 📶 Wi-Fi watchdog — state machine for monitoring, retry, fallback to hotspot
  • 🌐 Captive portal — first-boot or Wi-Fi loss → spin up an AP with web setup form
Agent binary itself is not auto-updated

The binary is installed by install-agent.sh and managed by systemd. Updating it requires a new agent-v* tag → CI rebuilds → reinstall via the install script.

Installation

One command on a fresh Raspberry Pi:

curl -fsSL https://raw.githubusercontent.com/alphaoflogic-ua/smart-home-updates/main/install-agent.sh | sudo bash

The script:

  1. Installs Docker if missing
  2. Downloads deployment files (docker-compose.yml, nginx/, .env)
  3. Configures the stack interactively (Wi-Fi, secrets, environment)
  4. Downloads the agent binary
  5. Installs and starts the station-agent.service systemd unit

Reset & Reinstall

curl -fsSL https://raw.githubusercontent.com/alphaoflogic-ua/smart-home-updates/main/reset-station.sh | sudo bash

Removes the systemd service, agent files, the entire Docker stack (with volumes), and the Docker images. Use with care — wipes all device data.

Docker Update Flow

release.json Format

{
"version": "1.2.3",
"images": {
"backend": "andriicode/smart-home-backend:1.2.3",
"frontend": "andriicode/smart-home-frontend:1.2.3"
}
}

CI updates this file in smart-home-updates repo on every release tag.

Firmware OTA Flow

Wi-Fi Watchdog

State machine that keeps the Pi connected, falling back to a hotspot for re-configuration:

Captive Portal

When entering HOTSPOT_ACTIVE:

  1. NetworkManager spins up a Wi-Fi access point
  2. Nginx is stopped to free port 80
  3. Agent serves a setup page at port 80 with a Wi-Fi network picker
  4. Captive portal detection (iOS/Android/Windows/macOS) auto-opens the page
  5. User submits SSID + password — agent connects, tears down hotspot, restarts nginx
  6. Wi-Fi credentials are forwarded to backend (/api/settings/agent) for ESP32 provisioning use

HTTP API

Default port: 3001. Mutating endpoints require Authorization: Bearer <AGENT_TOKEN> if AGENT_TOKEN is configured.

# Health
curl http://localhost:3001/health

# Full status (incl. firmware cache)
curl http://localhost:3001/status

# Current/last installed app version
curl http://localhost:3001/version

# Wi-Fi status (mode, SSID, IP, hotspot, watchdog state)
curl http://localhost:3001/wifi/status

# Force update to latest
curl -X POST http://localhost:3001/update \
-H 'Authorization: Bearer <token>'

# Force update to specific version
curl -X POST http://localhost:3001/update \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{"version": "1.2.3"}'

# Manual rollback
curl -X POST http://localhost:3001/rollback \
-H 'Authorization: Bearer <token>'

Environment Variables

Required

VariableDescription
STATION_IDStation UUID
UPDATE_SERVER_URLURL to release.json

Docker Updates

VariableDefaultDescription
COMPOSE_PROJECT_PATH~/smart-homePath to compose project
COMPOSE_FILEdocker-compose.ymlCompose file name
CHECK_INTERVAL_MINUTES60Update poll interval
AUTO_UPDATEtrueAuto-pull on new version
BOOTSTRAP_ON_STARTfalseStart stack on agent boot if down
HEALTHCHECK_URLhttp://localhost/api/healthPost-update healthcheck
STABILIZATION_SECONDS45Wait before healthcheck
DOCKER_USERNAME / DOCKER_TOKENDocker Hub creds (private images)
DOCKER_REGISTRYCustom registry
BACKEND_CONTAINER_NAMEsmart-home-backend
FRONTEND_CONTAINER_NAMEsmart-home-frontend

Firmware OTA

VariableDefaultDescription
FIRMWARE_MANIFEST_URL{UPDATE_SERVER_URL}/../firmware/manifest.jsonManifest URL
FIRMWARE_CACHE_DIR~/firmware-cacheWhere .bin files land
FIRMWARE_FOLDER/firmwareURL path served by nginx
FIRMWARE_CHECK_INTERVAL_MINUTES60Manifest poll interval
BACKEND_URLderived from HEALTHCHECK_URLAPI target
BACKEND_AGENT_TOKENBearer for /api/ota/*

Misc

VariableDefaultDescription
PORT3001Agent HTTP port
HOST127.0.0.1Bind address
AGENT_TOKENBearer for protected POST endpoints
DATA_DIR~/station-agent-dataAgent state directory

Filesystem Layout

/opt/station-agent/
station-agent ← SEA binary
.env ← agent config

/var/lib/station-agent/
current_version.txt
rollback.json
compose.rollback.override.yml
compose.update.override.yml

~/firmware-cache/
esp32-climate@0.1.3.bin
esp32-climate.json ← cached metadata { version, file, checksum }
esp32-pir@0.1.3.bin
...

~/smart-home/
docker-compose.yml
.env ← stack config
nginx/

Logs

sudo journalctl -u station-agent -f

Structured JSON logs:

{"time":"...","event":"update_start","currentVersion":"1.0.0","images":"..."}
{"time":"...","event":"healthcheck_passed","status":200}
{"time":"...","event":"update_completed","currentVersion":"1.2.3"}

Docker events: update_start, update_completed, update_available, docker_pull, docker_restart, healthcheck_passed, healthcheck_failed, rollback_started, rollback_completed, bootstrap_start, bootstrap_done.

Firmware events: firmware_download_start, firmware_download_done, firmware_update_available, firmware_ready, firmware_old_removed, firmware_check_retry, firmware_check_failed.

Reference