diff --git a/README.md b/README.md index 6832223..70019c4 100644 --- a/README.md +++ b/README.md @@ -3,38 +3,49 @@ **EdgeKit** is an open-source client/server IoT edge platform designed to run on [k3s](https://k3s.io/). It provides a central MQTT broker (server) and lightweight edge agents (clients) that collect telemetry and stream it to the server via MQTT over WebSocket. +EdgeKit integrates [EdgeX Foundry 4.0 (Odessa)](https://docs.edgexfoundry.org/4.0/) to add full IoT device management, Modbus connectivity, and centrally-managed security services. + --- ## Architecture ``` -┌─────────────────────────────────────────┐ -│ k3s / Kubernetes │ -│ │ -│ ┌───────────────────────────────────┐ │ -│ │ edgekit-server │ │ -│ │ Eclipse Mosquitto MQTT broker │ │ -│ │ • port 1883 – plain MQTT │ │ -│ │ • port 9001 – MQTT over WS │ │ -│ └───────────────────────────────────┘ │ -│ ▲ ▲ │ -│ │ WebSocket │ │ -│ ┌─────────┴──┐ ┌──────┴─────────┐ │ -│ │ client 1 │ │ client N │ │ -│ │ edge agent │ │ edge agent │ │ -│ └────────────┘ └────────────────┘ │ -└─────────────────────────────────────────┘ +┌──────────────────────────────────────────────────────────┐ +│ Central Server │ +│ │ +│ ┌───────────────────────────────────────────────────┐ │ +│ │ edgekit-server (Eclipse Mosquitto MQTT broker) │ │ +│ │ • port 1883 – plain MQTT │ │ +│ │ • port 9001 – MQTT over WS │ │ +│ └───────────────────────────────────────────────────┘ │ +│ ┌───────────────────────────────────────────────────┐ │ +│ │ EdgeX Security Services │ │ +│ │ • secret-store (OpenBao) :8200 │ │ +│ │ • nginx API gateway :8443 │ │ +│ └───────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────┘ + ▲ WebSocket / MQTT ▲ WebSocket / MQTT + │ │ +┌────────┴───────┐ ┌────────┴────────────────┐ +│ Full k3s Node │ │ Thin Edge Node │ +│ │ │ │ +│ edgekit-client │ │ edgekit-thin-client │ +│ EdgeX Core + │ │ (single container: │ +│ device-modbus │ │ EdgeX + PG + MQTT) │ +│ (10 containers)│ │ │ +└────────────────┘ └─────────────────────────┘ ``` -Each **client** is a single Docker container that: +Each **full k3s edge node** runs: -- Collects system metrics (CPU, memory, disk, network) -- Publishes JSON payloads to `edgekit//metrics` every 5 seconds (configurable) -- Automatically reconnects to the broker on failure +- `edgekit-client` – collects system metrics and publishes them to the central MQTT broker +- EdgeX Core Services (`core-data`, `core-metadata`, `core-command`) in separate containers +- EdgeX Supporting Services (`support-notifications`, `support-scheduler`) +- `device-modbus` – reads from Modbus TCP/RTU field devices -The **server** is a single Eclipse Mosquitto container with both plain MQTT (1883) and WebSocket (9001) listeners. +Each **thin edge node** runs a single `edgekit-thin-client` container that bundles all of the above (EdgeX + PostgreSQL + Mosquitto, managed by supervisord). It is hardened, standalone, and not extendable without rebuilding the image. -For a deeper dive, see [docs/architecture.md](docs/architecture.md). +For a deeper dive, see [docs/architecture.md](docs/architecture.md) and [docs/edgex.md](docs/edgex.md). --- @@ -135,36 +146,86 @@ See [helm/edgekit/values.yaml](helm/edgekit/values.yaml) for the full Helm confi --- -## Repository Layout +## EdgeX Foundry integration + +EdgeKit embeds [EdgeX Foundry 4.0 (Odessa)](https://docs.edgexfoundry.org/4.0/) for full IoT device management and Modbus connectivity. There are **two edge client variants**: + +### Full k3s edge node – start the EdgeX core stack + +```bash +./scripts/start-edgex-client.sh +``` + +Starts 10 separate containers: MQTT message bus, PostgreSQL, core-keeper, core-data, core-metadata, core-command, support-notifications, support-scheduler, device-modbus. Best for k3s clusters or development environments where services need to be extended. + +### Thin edge node – single-container EdgeX stack + +```bash +docker build -t edgekit-thin-client edgex-thin-client/ +./scripts/start-edgex-thin-client.sh --no-build +``` + +Starts **one hardened container** with all EdgeX services embedded (PostgreSQL + Mosquitto + all Core/Supporting/Device services, managed by supervisord). Standalone, not extendable without rebuilding. Best for resource-constrained or locked-down production devices. + +### Central server – start the EdgeX security stack + +```bash +./scripts/start-edgex-server.sh +``` + +Starts: OpenBao (secret store), security-bootstrapper, security-secretstore-setup, security-proxy-auth, security-proxy-setup, nginx (TLS API gateway on :8443). + +See [docs/edgex.md](docs/edgex.md) for the full integration guide. + +--- ``` edgekit/ -├── server/ # MQTT broker container +├── server/ # MQTT broker container │ ├── Dockerfile │ ├── mosquitto.conf │ └── entrypoint.sh -├── client/ # Edge agent container +├── client/ # Edge agent container │ ├── Dockerfile │ ├── package.json │ └── src/ │ └── index.js +├── edgex-thin-client/ # Single-container thin EdgeX client image +│ ├── Dockerfile # Multi-stage: extracts EdgeX binaries + Alpine base +│ ├── supervisord.conf # Manages all internal processes +│ ├── mosquitto.conf # Embedded MQTT (localhost only) +│ └── entrypoint.sh # PostgreSQL initdb + supervisord ├── helm/ -│ └── edgekit/ # Root Helm chart +│ └── edgekit/ # Root Helm chart │ ├── Chart.yaml -│ ├── values.yaml +│ ├── values.yaml # Default values +│ ├── values-client.yaml # Full k3s edge node overrides +│ ├── values-server.yaml # Central server overrides +│ ├── values-thin-client.yaml # Thin edge node overrides │ └── templates/ ├── scripts/ -│ ├── build.sh # Build Docker images -│ ├── start-local.sh # Start via docker compose -│ └── stop-local.sh # Stop local stack +│ ├── build.sh # Build Docker images +│ ├── start-local.sh # Start base stack via docker compose +│ ├── stop-local.sh # Stop base stack +│ ├── start-edgex-client.sh # Start full k3s EdgeX edge stack +│ ├── stop-edgex-client.sh # Stop full k3s EdgeX edge stack +│ ├── start-edgex-server.sh # Start EdgeX security stack +│ ├── stop-edgex-server.sh # Stop EdgeX security stack +│ ├── start-edgex-thin-client.sh # Build + start thin EdgeX client +│ └── stop-edgex-thin-client.sh # Stop thin EdgeX client ├── .github/ │ └── workflows/ -│ ├── ci.yml # CI: lint + build on PR/push to develop|main -│ └── release.yml # Release: push images + Helm chart on tag +│ ├── ci.yml # CI: lint + build on PR/push to develop|main +│ └── release.yml # Release: push images + Helm chart on tag ├── docs/ │ ├── architecture.md +│ ├── edgex.md # EdgeX integration guide │ └── quickstart.md -└── docker-compose.yml # Local development +├── docker-compose.yml # Base local stack (MQTT broker + edge agent) +├── docker-compose.edgex-client.yml # Full k3s EdgeX edge stack (no security) +├── docker-compose.edgex-thin-client.yml # Thin EdgeX single-container stack +├── docker-compose.edgex-server.yml # EdgeX security services +└── fleet.yaml # Rancher Fleet multi-cluster GitOps ``` --- diff --git a/docker-compose.edgex-client.yml b/docker-compose.edgex-client.yml new file mode 100644 index 0000000..fb8c050 --- /dev/null +++ b/docker-compose.edgex-client.yml @@ -0,0 +1,338 @@ +# ============================================================================= +# EdgeKit – EdgeX Foundry 4.0 (Odessa) – Client / Edge node stack +# +# Runs a minimal, no-security EdgeX platform on the edge device: +# • Core Services – core-keeper, core-common-config-bootstrapper, +# core-data, core-metadata, core-command +# • Supporting – support-notifications, support-scheduler +# • Device Services – device-modbus +# • Infrastructure – MQTT message bus, PostgreSQL +# +# Usage: +# docker compose -f docker-compose.edgex-client.yml up -d +# +# This compose file is intended for edge (client) nodes only. +# The central server runs docker-compose.edgex-server.yml instead. +# +# References: +# https://docs.edgexfoundry.org/4.0/ +# https://github.com/edgexfoundry/edgex-compose/blob/v4.0.0/docker-compose-no-secty.yml +# ============================================================================= +name: edgex-client + +services: + + # --------------------------------------------------------------------------- + # MQTT broker – internal EdgeX message bus + # --------------------------------------------------------------------------- + mqtt-broker: + image: eclipse-mosquitto:2.0.21 + container_name: edgex-mqtt-broker + hostname: edgex-mqtt-broker + command: + - /usr/sbin/mosquitto + - -c + - /mosquitto-no-auth.conf + ports: + - "127.0.0.1:1883:1883" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "2002:2001" + + # --------------------------------------------------------------------------- + # PostgreSQL – data store for core-data, core-metadata, core-keeper + # --------------------------------------------------------------------------- + database: + image: postgres:16.3-alpine3.20 + container_name: edgex-postgres + hostname: edgex-postgres + environment: + POSTGRES_DB: edgex_db + POSTGRES_PASSWORD: postgres + ports: + - "127.0.0.1:5432:5432" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + tmpfs: + - /run + volumes: + - /etc/localtime:/etc/localtime:ro + - db-data:/var/lib/postgresql/data + + # --------------------------------------------------------------------------- + # core-keeper – service registry (replaces Consul in EdgeX 4.0) + # --------------------------------------------------------------------------- + core-keeper: + image: edgexfoundry/core-keeper:4.0.0 + container_name: edgex-core-keeper + hostname: edgex-core-keeper + depends_on: + database: + condition: service_started + mqtt-broker: + condition: service_started + environment: + EDGEX_SECURITY_SECRET_STORE: "false" + SERVICE_HOST: edgex-core-keeper + DATABASE_HOST: edgex-postgres + MESSAGEBUS_TYPE: mqtt + MESSAGEBUS_PROTOCOL: tcp + MESSAGEBUS_HOST: edgex-mqtt-broker + MESSAGEBUS_PORT: "1883" + MESSAGEBUS_AUTHMODE: none + MESSAGEBUS_SECRETNAME: "" + ports: + - "127.0.0.1:59890:59890" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "2002:2001" + volumes: + - /etc/localtime:/etc/localtime:ro + + # --------------------------------------------------------------------------- + # core-common-config-bootstrapper – pushes shared config to core-keeper + # --------------------------------------------------------------------------- + core-common-config-bootstrapper: + image: edgexfoundry/core-common-config-bootstrapper:4.0.0 + container_name: edgex-core-common-config-bootstrapper + hostname: edgex-core-common-config-bootstrapper + command: + - /core-common-config-bootstrapper + - --registry + - -cp=keeper.http://edgex-core-keeper:59890 + depends_on: + core-keeper: + condition: service_started + environment: + EDGEX_SECURITY_SECRET_STORE: "false" + ALL_SERVICES_DATABASE_HOST: edgex-postgres + ALL_SERVICES_MESSAGEBUS_HOST: edgex-mqtt-broker + ALL_SERVICES_REGISTRY_HOST: edgex-core-keeper + APP_SERVICES_CLIENTS_CORE_METADATA_HOST: edgex-core-metadata + DEVICE_SERVICES_CLIENTS_CORE_METADATA_HOST: edgex-core-metadata + networks: + - edgex-network + read_only: true + security_opt: + - no-new-privileges:true + user: "2002:2001" + volumes: + - /etc/localtime:/etc/localtime:ro + + # --------------------------------------------------------------------------- + # core-metadata – device / profile / service registry + # --------------------------------------------------------------------------- + core-metadata: + image: edgexfoundry/core-metadata:4.0.0 + container_name: edgex-core-metadata + hostname: edgex-core-metadata + command: + - --registry + - -cp=keeper.http://edgex-core-keeper:59890 + depends_on: + core-keeper: + condition: service_started + core-common-config-bootstrapper: + condition: service_started + database: + condition: service_started + environment: + EDGEX_SECURITY_SECRET_STORE: "false" + SERVICE_HOST: edgex-core-metadata + ports: + - "127.0.0.1:59881:59881" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "2002:2001" + volumes: + - /etc/localtime:/etc/localtime:ro + + # --------------------------------------------------------------------------- + # core-data – event / reading persistence and forwarding + # --------------------------------------------------------------------------- + core-data: + image: edgexfoundry/core-data:4.0.0 + container_name: edgex-core-data + hostname: edgex-core-data + command: + - --registry + - -cp=keeper.http://edgex-core-keeper:59890 + depends_on: + core-keeper: + condition: service_started + core-common-config-bootstrapper: + condition: service_started + database: + condition: service_started + environment: + EDGEX_SECURITY_SECRET_STORE: "false" + SERVICE_HOST: edgex-core-data + ports: + - "127.0.0.1:59880:59880" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "2002:2001" + volumes: + - /etc/localtime:/etc/localtime:ro + + # --------------------------------------------------------------------------- + # core-command – REST / MQTT command interface for devices + # --------------------------------------------------------------------------- + core-command: + image: edgexfoundry/core-command:4.0.0 + container_name: edgex-core-command + hostname: edgex-core-command + command: + - --registry + - -cp=keeper.http://edgex-core-keeper:59890 + depends_on: + core-keeper: + condition: service_started + core-common-config-bootstrapper: + condition: service_started + core-metadata: + condition: service_started + database: + condition: service_started + environment: + EDGEX_SECURITY_SECRET_STORE: "false" + SERVICE_HOST: edgex-core-command + EXTERNALMQTT_URL: tcp://edgex-mqtt-broker:1883 + ports: + - "127.0.0.1:59882:59882" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "2002:2001" + volumes: + - /etc/localtime:/etc/localtime:ro + + # --------------------------------------------------------------------------- + # support-notifications – alerting and notification service + # --------------------------------------------------------------------------- + support-notifications: + image: edgexfoundry/support-notifications:4.0.0 + container_name: edgex-support-notifications + hostname: edgex-support-notifications + command: + - --registry + - -cp=keeper.http://edgex-core-keeper:59890 + depends_on: + core-keeper: + condition: service_started + core-common-config-bootstrapper: + condition: service_started + database: + condition: service_started + environment: + EDGEX_SECURITY_SECRET_STORE: "false" + SERVICE_HOST: edgex-support-notifications + ports: + - "127.0.0.1:59860:59860" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "2002:2001" + volumes: + - /etc/localtime:/etc/localtime:ro + + # --------------------------------------------------------------------------- + # support-scheduler – job scheduling service + # --------------------------------------------------------------------------- + support-scheduler: + image: edgexfoundry/support-scheduler:4.0.0 + container_name: edgex-support-scheduler + hostname: edgex-support-scheduler + command: + - --registry + - -cp=keeper.http://edgex-core-keeper:59890 + depends_on: + core-keeper: + condition: service_started + core-common-config-bootstrapper: + condition: service_started + database: + condition: service_started + environment: + EDGEX_SECURITY_SECRET_STORE: "false" + SERVICE_HOST: edgex-support-scheduler + ports: + - "127.0.0.1:59863:59863" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "2002:2001" + volumes: + - /etc/localtime:/etc/localtime:ro + + # --------------------------------------------------------------------------- + # device-modbus – Modbus (TCP/RTU) device service + # --------------------------------------------------------------------------- + device-modbus: + image: edgexfoundry/device-modbus:4.0.0 + container_name: edgex-device-modbus + hostname: edgex-device-modbus + command: + - -cp=keeper.http://edgex-core-keeper:59890 + - --registry + depends_on: + core-keeper: + condition: service_started + core-common-config-bootstrapper: + condition: service_started + core-data: + condition: service_started + core-metadata: + condition: service_started + environment: + EDGEX_SECURITY_SECRET_STORE: "false" + SERVICE_HOST: edgex-device-modbus + ports: + - "127.0.0.1:59901:59901" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "2002:2001" + volumes: + - /etc/localtime:/etc/localtime:ro + +networks: + edgex-network: + name: edgex_edgex-network + driver: bridge + +volumes: + db-data: + name: edgex_db-data diff --git a/docker-compose.edgex-server.yml b/docker-compose.edgex-server.yml new file mode 100644 index 0000000..f9e0620 --- /dev/null +++ b/docker-compose.edgex-server.yml @@ -0,0 +1,357 @@ +# ============================================================================= +# EdgeKit – EdgeX Foundry 4.0 (Odessa) – Server / Central node security stack +# +# Runs the EdgeX Security Services on the central server: +# • mqtt-broker – internal message bus (required by core-keeper) +# • database – PostgreSQL (required by core-keeper) +# • core-keeper – service registry (required by security services) +# • secret-store – OpenBao (secret store, open-source Vault fork) +# • security-bootstrapper – orchestrates the secure startup sequence +# • security-secretstore-setup – initialises OpenBao and mints service tokens +# • security-proxy-setup – configures the nginx API gateway +# • security-proxy-auth – JWT / token-based API authentication +# • nginx – TLS-terminating reverse proxy (API gateway) +# +# Usage: +# docker compose -f docker-compose.edgex-server.yml up -d +# +# This compose file is intended for the central (server) node only. +# Edge nodes run docker-compose.edgex-client.yml instead. +# +# References: +# https://docs.edgexfoundry.org/4.0/security/Ch-Security/ +# https://github.com/edgexfoundry/edgex-compose/blob/v4.0.0/docker-compose.yml +# ============================================================================= +name: edgex-server + +# Shared environment block – stage-gate coordinates the secure boot sequence +x-stagegate-env: &stagegate-env + STAGEGATE_BOOTSTRAPPER_HOST: edgex-security-bootstrapper + STAGEGATE_BOOTSTRAPPER_STARTPORT: "54321" + STAGEGATE_DATABASE_HOST: edgex-postgres + STAGEGATE_DATABASE_PORT: "5432" + STAGEGATE_DATABASE_READYPORT: "5432" + STAGEGATE_PROXYSETUP_READYPORT: "54325" + STAGEGATE_READY_TORUNPORT: "54329" + STAGEGATE_REGISTRY_HOST: edgex-core-keeper + STAGEGATE_REGISTRY_PORT: "59890" + STAGEGATE_REGISTRY_READYPORT: "54324" + STAGEGATE_SECRETSTORESETUP_HOST: edgex-security-secretstore-setup + STAGEGATE_SECRETSTORESETUP_TOKENS_READYPORT: "54322" + STAGEGATE_WAITFOR_TIMEOUT: 60s + PROXY_SETUP_HOST: edgex-security-proxy-setup + SECRETSTORE_HOST: edgex-secret-store + CLIENTS_SECURITY_SECRETSTORE_SETUP_HOST: edgex-security-secretstore-setup + +services: + + # --------------------------------------------------------------------------- + # mqtt-broker – internal EdgeX message bus (required by core-keeper) + # --------------------------------------------------------------------------- + mqtt-broker: + image: eclipse-mosquitto:2.0.21 + container_name: edgex-mqtt-broker + hostname: edgex-mqtt-broker + command: + - /usr/sbin/mosquitto + - -c + - /mosquitto-no-auth.conf + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "2002:2001" + + # --------------------------------------------------------------------------- + # database – PostgreSQL data store for core-keeper + # --------------------------------------------------------------------------- + database: + image: postgres:16.3-alpine3.20 + container_name: edgex-postgres + hostname: edgex-postgres + environment: + POSTGRES_DB: edgex_db + POSTGRES_PASSWORD: postgres + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + tmpfs: + - /run + volumes: + - /etc/localtime:/etc/localtime:ro + - db-data:/var/lib/postgresql/data + + # --------------------------------------------------------------------------- + # core-keeper – service registry; required by security-proxy-auth and the + # stage-gate wait scripts + # --------------------------------------------------------------------------- + core-keeper: + image: edgexfoundry/core-keeper:4.0.0 + container_name: edgex-core-keeper + hostname: edgex-core-keeper + entrypoint: + - /bin/sh + - /edgex-init/ready_to_run_wait_install.sh + command: + - entrypoint.sh + - /core-keeper + depends_on: + database: + condition: service_started + mqtt-broker: + condition: service_started + security-secretstore-setup: + condition: service_started + environment: + <<: *stagegate-env + EDGEX_SECURITY_SECRET_STORE: "true" + SERVICE_HOST: edgex-core-keeper + DATABASE_HOST: edgex-postgres + MESSAGEBUS_TYPE: mqtt + MESSAGEBUS_PROTOCOL: tcp + MESSAGEBUS_HOST: edgex-mqtt-broker + MESSAGEBUS_PORT: "1883" + MESSAGEBUS_AUTHMODE: none + MESSAGEBUS_SECRETNAME: "" + ports: + - "127.0.0.1:59890:59890" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "2002:2001" + volumes: + - /etc/localtime:/etc/localtime:ro + - edgex-init:/edgex-init:ro + - /tmp/edgex/secrets/core-keeper:/tmp/edgex/secrets/core-keeper:ro,z + + # --------------------------------------------------------------------------- + # security-bootstrapper – creates /edgex-init volume, seeds network keys, + # and coordinates the multi-stage secure startup sequence + # --------------------------------------------------------------------------- + security-bootstrapper: + image: edgexfoundry/security-bootstrapper:4.0.0 + container_name: edgex-security-bootstrapper + hostname: edgex-security-bootstrapper + environment: + <<: *stagegate-env + EDGEX_USER: "2002" + EDGEX_GROUP: "2001" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "root:root" + volumes: + - /etc/localtime:/etc/localtime:ro + - edgex-init:/edgex-init + + # --------------------------------------------------------------------------- + # secret-store – OpenBao (open-source HashiCorp Vault fork) + # Stores all service secrets and credentials + # --------------------------------------------------------------------------- + secret-store: + image: openbao/openbao:2.1.1 + container_name: edgex-secret-store + hostname: edgex-secret-store + command: + - server + entrypoint: + - /edgex-init/secretstore_wait_install.sh + depends_on: + security-bootstrapper: + condition: service_started + environment: + <<: *stagegate-env + SKIP_SETCAP: "true" + BAO_ADDR: http://edgex-secret-store:8200 + BAO_CONFIG_DIR: /openbao/config + BAO_LOCAL_CONFIG: | + listener "tcp" { + address = "edgex-secret-store:8200" + tls_disable = "1" + cluster_address = "edgex-secret-store:8201" + } + backend "file" { + path = "/openbao/file" + } + default_lease_ttl = "168h" + max_lease_ttl = "720h" + disable_mlock = true + ports: + - "127.0.0.1:8200:8200" + networks: + - edgex-network + restart: always + tmpfs: + - /openbao/config + user: "root:root" + volumes: + - edgex-init:/edgex-init:ro + - secret-store-file:/openbao/file + - secret-store-logs:/openbao/logs + + # --------------------------------------------------------------------------- + # security-secretstore-setup – seeds OpenBao with all EdgeX service tokens + # --------------------------------------------------------------------------- + security-secretstore-setup: + image: edgexfoundry/security-secretstore-setup:4.0.0 + container_name: edgex-security-secretstore-setup + hostname: edgex-security-secretstore-setup + entrypoint: + - /edgex-init/secretstore_setup_wait_install.sh + depends_on: + secret-store: + condition: service_started + environment: + <<: *stagegate-env + EDGEX_SECURITY_SECRET_STORE: "true" + EDGEX_USER: "2002" + EDGEX_GROUP: "2001" + SECRETSTORE_HOST: edgex-secret-store + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "root:root" + volumes: + - /etc/localtime:/etc/localtime:ro + - edgex-init:/edgex-init:ro + - /tmp/edgex/secrets:/tmp/edgex/secrets:z + + # --------------------------------------------------------------------------- + # security-proxy-auth – JWT / API-token authentication service + # --------------------------------------------------------------------------- + security-proxy-auth: + image: edgexfoundry/security-proxy-auth:4.0.0 + container_name: edgex-proxy-auth + hostname: edgex-proxy-auth + command: + - entrypoint.sh + - /security-proxy-auth + - --registry + - -cp=keeper.http://edgex-core-keeper:59890 + entrypoint: + - /bin/sh + - /edgex-init/ready_to_run_wait_install.sh + depends_on: + security-secretstore-setup: + condition: service_started + environment: + <<: *stagegate-env + EDGEX_SECURITY_SECRET_STORE: "true" + SERVICE_HOST: edgex-proxy-auth + ports: + - "127.0.0.1:59842:59842" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + volumes: + - /etc/localtime:/etc/localtime:ro + - edgex-init:/edgex-init:ro + - /tmp/edgex/secrets/security-proxy-auth:/tmp/edgex/secrets/security-proxy-auth:ro,z + + # --------------------------------------------------------------------------- + # security-proxy-setup – configures the nginx API gateway routes + # --------------------------------------------------------------------------- + security-proxy-setup: + image: edgexfoundry/security-proxy-setup:4.0.0 + container_name: edgex-security-proxy-setup + hostname: edgex-security-proxy-setup + entrypoint: + - /edgex-init/proxy_setup_wait_install.sh + depends_on: + security-bootstrapper: + condition: service_started + security-secretstore-setup: + condition: service_started + environment: + <<: *stagegate-env + EDGEX_SECURITY_SECRET_STORE: "true" + # Route only the services present in this server stack through the proxy + EDGEX_ADD_PROXY_ROUTE: >- + core-keeper.http://edgex-core-keeper:59890 + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "root:root" + volumes: + - /etc/localtime:/etc/localtime:ro + - edgex-init:/edgex-init:ro + - nginx-tls:/etc/ssl/nginx + - nginx-templates:/etc/nginx/templates + + # --------------------------------------------------------------------------- + # nginx – TLS-terminating reverse proxy / API gateway + # Exposes EdgeX APIs externally on port 8443 (HTTPS) + # --------------------------------------------------------------------------- + nginx: + image: nginx:1.25.5-alpine-slim + container_name: edgex-nginx + hostname: edgex-nginx + command: + - /docker-entrypoint.sh + - nginx + - -g + - "daemon off;" + entrypoint: + - /bin/sh + - /edgex-init/nginx_wait_install.sh + depends_on: + security-secretstore-setup: + condition: service_started + environment: + <<: *stagegate-env + ports: + - "8443:8443" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + tmpfs: + - /etc/nginx/conf.d + - /var/cache/nginx + - /var/log/nginx + - /var/run + volumes: + - edgex-init:/edgex-init:ro + - nginx-templates:/etc/nginx/templates + - nginx-tls:/etc/ssl/nginx + +networks: + edgex-network: + name: edgex_edgex-network + driver: bridge + +volumes: + edgex-init: + name: edgex_edgex-init + db-data: + name: edgex_db-data + secret-store-file: + name: edgex_secret-store-file + secret-store-logs: + name: edgex_secret-store-logs + nginx-tls: + name: edgex_nginx-tls + nginx-templates: + name: edgex_nginx-templates diff --git a/docker-compose.edgex-thin-client.yml b/docker-compose.edgex-thin-client.yml new file mode 100644 index 0000000..9e3d195 --- /dev/null +++ b/docker-compose.edgex-thin-client.yml @@ -0,0 +1,79 @@ +# ============================================================================= +# docker-compose.edgex-thin-client.yml +# +# EdgeKit Thin Client – single-container EdgeX Foundry 4.0 edge node +# +# All EdgeX Core Services, Supporting Services, device-modbus, an embedded +# PostgreSQL database, and an Eclipse Mosquitto MQTT message bus run inside +# ONE container. This configuration is: +# +# • Standalone – no external dependencies; works anywhere Docker is present +# • Hardened – read-only root FS; data on named volume; no new privileges +# • Fixed – service set baked into the image; not extendable without +# rebuilding (contrast: docker-compose.edgex-client.yml) +# +# Prerequisites: +# Build the image first: +# docker build -t edgekit-thin-client edgex-thin-client/ +# +# Usage: +# docker compose -f docker-compose.edgex-thin-client.yml up -d +# # smoke tests: +# curl http://localhost:59890/api/v3/ping # core-keeper +# curl http://localhost:59880/api/v3/ping # core-data +# curl http://localhost:59881/api/v3/ping # core-metadata +# curl http://localhost:59882/api/v3/ping # core-command +# curl http://localhost:59860/api/v3/ping # support-notifications +# curl http://localhost:59863/api/v3/ping # support-scheduler +# curl http://localhost:59901/api/v3/ping # device-modbus +# ============================================================================= + +services: + edgex-thin: + image: edgekit-thin-client:latest + container_name: edgex-thin + restart: unless-stopped + + # Run as root so entrypoint.sh can chown the PostgreSQL data directory; + # supervisord drops EdgeX services to the edgex user (uid 2002), + # mosquitto to the mosquitto user, and PostgreSQL to the postgres user. + user: "0:0" + + # Hardening + security_opt: + - no-new-privileges:true + read_only: true + tmpfs: + # Writable scratch space for sockets, PIDs, and runtime temp files + - /tmp:mode=1777,size=64m + - /run:mode=0755,size=16m + - /run/postgresql:mode=0755,uid=70,size=4m + + # Persistent data (PostgreSQL + optional Mosquitto persistence) + volumes: + - edgex-thin-db:/data/postgresql + - edgex-thin-mosquitto:/data/mosquitto + + # Expose EdgeX REST API ports (bind to localhost for added security) + # Internal MQTT (1883) is intentionally NOT published. + ports: + - "127.0.0.1:59890:59890" # core-keeper + - "127.0.0.1:59880:59880" # core-data + - "127.0.0.1:59881:59881" # core-metadata + - "127.0.0.1:59882:59882" # core-command + - "127.0.0.1:59860:59860" # support-notifications + - "127.0.0.1:59863:59863" # support-scheduler + - "127.0.0.1:59901:59901" # device-modbus + + # Resource limits – appropriate for a constrained edge device + mem_limit: 512m + cpus: "1.0" + + environment: + TZ: UTC + +volumes: + edgex-thin-db: + driver: local + edgex-thin-mosquitto: + driver: local diff --git a/docs/architecture.md b/docs/architecture.md index 4da2036..633d844 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -112,6 +112,75 @@ The Helm chart at `helm/edgekit/` provisions all of the above resources. The cli --- +## EdgeX Foundry integration + +EdgeKit integrates [EdgeX Foundry 4.0 (Odessa)](https://docs.edgexfoundry.org/4.0/) to provide a full IoT edge computing platform. + +### Full k3s edge node (`edgekit/role=edge`) + +The `docker-compose.edgex-client.yml` file adds a minimal, no-security EdgeX stack to the edge node using separate containers — one per service — suitable for k3s or development use: + +``` +Full k3s edge node +│ +├── edgekit-client – Edge agent (MQTT telemetry to central server) +└── EdgeX (no-security, separate containers) + ├── mqtt-broker – Eclipse Mosquitto (EdgeX internal message bus) + ├── database – PostgreSQL + ├── core-keeper – Service registry + ├── core-common-config-bootstrapper + ├── core-data – Event / reading persistence :59880 + ├── core-metadata – Device / profile registry :59881 + ├── core-command – Device command service :59882 + ├── support-notifications – Alerting :59860 + ├── support-scheduler – Job scheduling :59863 + └── device-modbus – Modbus TCP/RTU device service :59901 +``` + +### Thin edge node (`edgekit/role=edge-thin`) + +The thin client bundles all of the above into **one hardened, standalone container** (`edgekit-thin-client`). It exposes the same EdgeX API ports but is not extendable without rebuilding the image: + +``` +Thin edge node +│ +└── edgekit-thin-client (ONE container) + └── supervisord manages internally: + ├── PostgreSQL – embedded database (localhost:5432) + ├── Mosquitto – embedded message bus (localhost:1883, not published) + ├── core-keeper :59890 + ├── core-common-config-bootstrapper (one-shot) + ├── core-data :59880 + ├── core-metadata :59881 + ├── core-command :59882 + ├── support-notifications :59860 + ├── support-scheduler :59863 + └── device-modbus :59901 +``` + +See [`edgex-thin-client/Dockerfile`](../edgex-thin-client/Dockerfile) for the multi-stage build. + +### Central server + +The `docker-compose.edgex-server.yml` file adds the EdgeX Security Services to the central server: + +``` +Central server +│ +├── edgekit-server – Eclipse Mosquitto MQTT broker :1883 / :9001 +└── EdgeX Security Services + ├── secret-store – OpenBao (Vault-compatible) :8200 + ├── security-bootstrapper + ├── security-secretstore-setup + ├── security-proxy-auth – API authentication :59842 + ├── security-proxy-setup + └── nginx – TLS API gateway :8443 +``` + +See [docs/edgex.md](edgex.md) for a full guide including Helm and Rancher Fleet deployment. + +--- + ## Extending EdgeKit The architecture is intentionally minimal. Common extensions: @@ -120,3 +189,4 @@ The architecture is intentionally minimal. Common extensions: - **Add a dashboard**: Deploy [MQTT Explorer](https://mqtt-explorer.com/) or [Grafana + EMQX](https://docs.emqx.com/en/emqx/latest/dashboard/introduction.html) as additional pods. - **Custom metrics**: Extend `client/src/index.js` to publish additional data (GPIO readings, custom sensors, application logs, etc.). - **Multiple namespaces**: Deploy the chart multiple times with different `MQTT_TOPIC_PREFIX` values. +- **EdgeX rules engine**: Add `app-rules-engine` (eKuiper) to the edge stack to filter and transform device readings before forwarding. diff --git a/docs/edgex.md b/docs/edgex.md new file mode 100644 index 0000000..c4d40f5 --- /dev/null +++ b/docs/edgex.md @@ -0,0 +1,270 @@ +# EdgeX Foundry Integration + +EdgeKit integrates [EdgeX Foundry 4.0 (Odessa)](https://docs.edgexfoundry.org/4.0/) to provide a full IoT edge computing platform alongside the existing MQTT telemetry pipeline. + +--- + +## Architecture overview + +``` +Central Server (edgekit/role=central) +│ +├── edgekit-server – Eclipse Mosquitto MQTT broker (telemetry) +│ └── ports 1883, 9001 +│ +└── EdgeX Security Services + ├── secret-store – OpenBao (secret store, Vault-compatible) + ├── security-bootstrapper + ├── security-secretstore-setup + ├── security-proxy-auth – JWT / API-token authentication + ├── security-proxy-setup + └── nginx – TLS API gateway (port 8443) + + +Full k3s Edge Node (edgekit/role=edge) +│ +├── edgekit-client – Edge agent (publishes metrics to central MQTT) +│ +└── EdgeX Core + Device Services – separate containers, no-security mode + ├── mqtt-broker – Eclipse Mosquitto (EdgeX internal message bus) + ├── database – PostgreSQL (data + metadata store) + ├── core-keeper – Service registry (replaces Consul in 4.0) + ├── core-common-config-bootstrapper + ├── core-data – Event / reading persistence (port 59880) + ├── core-metadata – Device / profile registry (port 59881) + ├── core-command – REST + MQTT device command (port 59882) + ├── support-notifications – Alerting (port 59860) + ├── support-scheduler – Job scheduler (port 59863) + └── device-modbus – Modbus TCP/RTU device service (port 59901) + + +Thin Edge Node (edgekit/role=edge-thin) +│ +└── edgekit-thin-client – ONE container; all of the above bundled inside + (PostgreSQL + Mosquitto + all EdgeX services + managed by supervisord; hardened, standalone) +``` + +--- + +## Edge client variants + +EdgeKit provides **two EdgeX client variants**. Both expose identical EdgeX APIs on the same ports and carry the same functional scope. They differ in architecture and deployment characteristics. + +| | Full k3s client | Thin client | +|---|---|---| +| **Compose file** | `docker-compose.edgex-client.yml` | `docker-compose.edgex-thin-client.yml` | +| **Image** | Official EdgeX images (one per service) | Single `edgekit-thin-client` image | +| **Containers** | 10 containers | 1 container | +| **Process manager** | Docker Compose / k3s | supervisord (internal) | +| **Database** | External `postgres:16` container | Embedded PostgreSQL 16 | +| **Message bus** | External Mosquitto container | Embedded Mosquitto (localhost) | +| **Extendable** | ✅ Add services via compose overrides | ❌ Fixed service set; rebuild to extend | +| **Hardened** | Standard Docker security defaults | `read_only`, `no-new-privileges`, minimal Alpine | +| **Fleet label** | `edgekit/role=edge` | `edgekit/role=edge-thin` | +| **Helm values** | `values-client.yaml` | `values-thin-client.yaml` | +| **Best for** | Dev, k3s clusters, extensible deployments | Resource-constrained devices, locked-down production | + +--- + +## EdgeX service roles + +### Core Services + +| Service | Port | Description | +|---------|------|-------------| +| `core-keeper` | 59890 | Service registry; replaces Consul in EdgeX 4.0 | +| `core-common-config-bootstrapper` | – | Pushes shared configuration to core-keeper | +| `core-data` | 59880 | Receives, stores, and forwards device events/readings | +| `core-metadata` | 59881 | Registry for device profiles, devices, and device services | +| `core-command` | 59882 | Issues commands to devices via REST or external MQTT | + +### Supporting Services + +| Service | Port | Description | +|---------|------|-------------| +| `support-notifications` | 59860 | Sends alerts via email, Slack, webhook, etc. | +| `support-scheduler` | 59863 | Schedules recurring actions (e.g. periodic reads) | + +### Device Services + +| Service | Port | Description | +|---------|------|-------------| +| `device-modbus` | 59901 | Reads from and writes to Modbus TCP/RTU devices | + +### Security Services (central server only) + +| Service | Port | Description | +|---------|------|-------------| +| `secret-store` (OpenBao) | 8200 | Secret store – holds all service credentials and tokens | +| `security-bootstrapper` | – | Orchestrates the secure startup sequence | +| `security-secretstore-setup` | – | Initialises OpenBao; mints per-service tokens | +| `security-proxy-auth` | 59842 | Validates JWT and API tokens for API requests | +| `security-proxy-setup` | – | Configures nginx routes for all EdgeX services | +| `nginx` | 8443 | TLS-terminating reverse proxy; only entry point to EdgeX APIs | + +--- + +## Running locally with Docker Compose + +### Full k3s edge node stack (no security, multi-container) + +```bash +# Start EdgeX core + device services +./scripts/start-edgex-client.sh + +# Verify all services are healthy +curl http://localhost:59880/api/v3/ping # core-data +curl http://localhost:59881/api/v3/ping # core-metadata +curl http://localhost:59882/api/v3/ping # core-command +curl http://localhost:59901/api/v3/ping # device-modbus + +# Stop +./scripts/stop-edgex-client.sh +``` + +### Thin edge node (single container, hardened) + +```bash +# Build the image once +docker build -t edgekit-thin-client edgex-thin-client/ + +# Start the thin client +./scripts/start-edgex-thin-client.sh --no-build # skip build if already built + +# Verify all services are healthy (same ports as full client) +curl http://localhost:59890/api/v3/ping # core-keeper +curl http://localhost:59880/api/v3/ping # core-data +curl http://localhost:59881/api/v3/ping # core-metadata +curl http://localhost:59882/api/v3/ping # core-command +curl http://localhost:59901/api/v3/ping # device-modbus + +# Stop +./scripts/stop-edgex-thin-client.sh + +# Stop and wipe persistent data +./scripts/stop-edgex-thin-client.sh --purge +``` + +### Central server stack (security services) + +```bash +# Start EdgeX security services +./scripts/start-edgex-server.sh + +# Verify the secret store is running +curl http://localhost:8200/v1/sys/health + +# Stop +./scripts/stop-edgex-server.sh +``` + +--- + +## Kubernetes / Helm deployment + +### Full k3s edge cluster + +```bash +helm install edgekit ./helm/edgekit \ + --namespace edgekit \ + --create-namespace \ + -f helm/edgekit/values-client.yaml +``` + +This deploys only the **edge agent** (`edgekit-client`) and disables the broker. The agent connects to the central MQTT broker. + +### Thin edge cluster + +```bash +helm install edgekit ./helm/edgekit \ + --namespace edgekit \ + --create-namespace \ + -f helm/edgekit/values-thin-client.yaml +``` + +This deploys the **single-container thin EdgeX client** (`edgekit-thin-client`) with a dedicated PersistentVolumeClaim for PostgreSQL data. + +### Central cluster + +```bash +helm install edgekit ./helm/edgekit \ + --namespace edgekit \ + --create-namespace \ + -f helm/edgekit/values-server.yaml +``` + +This deploys only the **MQTT broker** (`edgekit-server`) with a `LoadBalancer` service so edge nodes can connect. + +--- + +## Rancher Fleet (GitOps) + +The `fleet.yaml` at the repository root enables GitOps-driven multi-cluster deployment with [Rancher Fleet](https://fleet.rancher.io/). + +Label your clusters before importing: + +```bash +# Central server cluster +kubectl label cluster edgekit/role=central + +# Full k3s edge node clusters +kubectl label cluster edgekit/role=edge +kubectl label cluster edgekit/role=edge + +# Thin edge node clusters (single-container, locked-down) +kubectl label cluster edgekit/role=edge-thin +``` + +Fleet will automatically apply: +- `values-server.yaml` to `edgekit/role=central` clusters +- `values-client.yaml` to `edgekit/role=edge` clusters +- `values-thin-client.yaml` to `edgekit/role=edge-thin` clusters + +--- + +## Modbus device configuration + +The `device-modbus` service discovers its device profiles and device configuration from `core-metadata`. To add a Modbus device: + +1. Create a device profile (YAML) describing the device's resources and commands. +2. POST the profile to `core-metadata`: + +```bash +curl -X POST http://localhost:59881/api/v3/deviceprofile/uploadfile \ + -F "file=@my-modbus-profile.yaml" +``` + +3. Register the device: + +```bash +curl -X POST http://localhost:59881/api/v3/device \ + -H "Content-Type: application/json" \ + -d '{ + "apiVersion": "v3", + "device": { + "name": "my-modbus-device", + "profileName": "my-modbus-profile", + "serviceName": "device-modbus", + "protocols": { + "modbus-tcp": { + "Address": "192.168.1.100", + "Port": "502", + "UnitID": "1" + } + } + } + }' +``` + +See the [EdgeX device-modbus documentation](https://docs.edgexfoundry.org/4.0/microservices/device/services/device-modbus/General/) for full details. + +--- + +## References + +- [EdgeX Foundry 4.0 documentation](https://docs.edgexfoundry.org/4.0/) +- [EdgeX compose files (v4.0.0)](https://github.com/edgexfoundry/edgex-compose/tree/v4.0.0) +- [device-modbus documentation](https://docs.edgexfoundry.org/4.0/microservices/device/services/device-modbus/General/) +- [EdgeX security architecture](https://docs.edgexfoundry.org/4.0/security/Ch-Security/) +- [OpenBao (secret store)](https://openbao.org/) diff --git a/edgex-thin-client/Dockerfile b/edgex-thin-client/Dockerfile new file mode 100644 index 0000000..a1b900f --- /dev/null +++ b/edgex-thin-client/Dockerfile @@ -0,0 +1,114 @@ +# ============================================================================= +# EdgeKit Thin Client – Single-container EdgeX Foundry 4.0 +# +# Bundles all EdgeX Core Services, Supporting Services, and device-modbus +# into ONE hardened Docker image, together with an embedded PostgreSQL 16 +# database and an Eclipse Mosquitto MQTT message bus. +# +# Key characteristics: +# • Standalone – no external service dependencies; run with a single +# `docker run` or one-line docker compose service +# • Hardened – read-only root FS (data on tmpfs/volume), non-root +# EdgeX processes, no-new-privileges, minimal Alpine base +# • Fixed – service set is baked in; not extendable at runtime; +# add new device services by rebuilding the image +# +# Contrast with the full k3s client (docker-compose.edgex-client.yml) which +# uses separate containers per service and is extendable via compose overrides. +# +# Build: +# docker build -t edgekit-thin-client edgex-thin-client/ +# +# References: +# https://docs.edgexfoundry.org/4.0/ +# ============================================================================= + +# ── Stage 1: extract EdgeX Go binaries and default configs ─────────────────── +FROM edgexfoundry/core-keeper:4.0.0 AS core-keeper +FROM edgexfoundry/core-common-config-bootstrapper:4.0.0 AS core-ccb +FROM edgexfoundry/core-data:4.0.0 AS core-data +FROM edgexfoundry/core-metadata:4.0.0 AS core-metadata +FROM edgexfoundry/core-command:4.0.0 AS core-command +FROM edgexfoundry/support-notifications:4.0.0 AS support-notifications +FROM edgexfoundry/support-scheduler:4.0.0 AS support-scheduler +FROM edgexfoundry/device-modbus:4.0.0 AS device-modbus + +# ── Stage 2: assemble the thin client image ─────────────────────────────────── +FROM alpine:3.20 + +LABEL org.opencontainers.image.title="edgekit-thin-client" +LABEL org.opencontainers.image.description="Standalone, hardened single-container EdgeX Foundry 4.0 edge node (Core Services + Supporting Services + device-modbus)" +LABEL org.opencontainers.image.source="https://github.com/perspikapps/edgekit" + +# Install runtime deps: +# • postgresql16 – embedded database for core-data / core-metadata / core-keeper +# • mosquitto – embedded MQTT message bus (EdgeX internal bus) +# • py3-supervisor – supervisord process manager (manages all EdgeX processes) +# • su-exec – privilege-drop helper used by entrypoint +# • tzdata – timezone data +RUN apk add --no-cache \ + postgresql16 \ + mosquitto \ + py3-supervisor \ + su-exec \ + tzdata \ + && mkdir -p \ + /edgex/bin \ + /edgex/services/core-keeper/res \ + /edgex/services/core-ccb/res \ + /edgex/services/core-data/res \ + /edgex/services/core-metadata/res \ + /edgex/services/core-command/res \ + /edgex/services/support-notifications/res \ + /edgex/services/support-scheduler/res \ + /edgex/services/device-modbus/res \ + /run/postgresql \ + /tmp/edgex/logs \ + && chown postgres:postgres /run/postgresql + +# ── Copy EdgeX binaries (statically compiled Go, run fine on Alpine/musl) ──── +COPY --from=core-keeper /core-keeper /edgex/bin/core-keeper +COPY --from=core-ccb /core-common-config-bootstrapper /edgex/bin/core-common-config-bootstrapper +COPY --from=core-data /core-data /edgex/bin/core-data +COPY --from=core-metadata /core-metadata /edgex/bin/core-metadata +COPY --from=core-command /core-command /edgex/bin/core-command +COPY --from=support-notifications /support-notifications /edgex/bin/support-notifications +COPY --from=support-scheduler /support-scheduler /edgex/bin/support-scheduler +COPY --from=device-modbus /device-modbus /edgex/bin/device-modbus + +# ── Copy EdgeX default resource / config directories ───────────────────────── +# Each service reads its configuration template from ./res/ relative to its CWD. +# The common-config-bootstrapper pushes shared config to core-keeper from its /res. +COPY --from=core-keeper /res/ /edgex/services/core-keeper/res/ +COPY --from=core-ccb /res/ /edgex/services/core-ccb/res/ +COPY --from=core-data /res/ /edgex/services/core-data/res/ +COPY --from=core-metadata /res/ /edgex/services/core-metadata/res/ +COPY --from=core-command /res/ /edgex/services/core-command/res/ +COPY --from=support-notifications /res/ /edgex/services/support-notifications/res/ +COPY --from=support-scheduler /res/ /edgex/services/support-scheduler/res/ +COPY --from=device-modbus /res/ /edgex/services/device-modbus/res/ + +# ── Copy thin-client configuration files ───────────────────────────────────── +COPY mosquitto.conf /etc/mosquitto/mosquitto.conf +COPY supervisord.conf /etc/supervisord.conf +COPY entrypoint.sh /entrypoint.sh + +RUN chmod +x /entrypoint.sh /edgex/bin/* + +# Create the dedicated EdgeX user/group (uid/gid 2002/2001 matches EdgeX convention) +RUN addgroup -g 2001 edgex \ + && adduser -u 2002 -G edgex -H -D edgex \ + && chown -R edgex:edgex /edgex /tmp/edgex + +# ── Volumes ─────────────────────────────────────────────────────────────────── +# /data/postgresql – persistent PostgreSQL data (survives container restarts) +# /data/mosquitto – optional Mosquitto persistence +VOLUME ["/data/postgresql", "/data/mosquitto"] + +# ── Exposed ports ───────────────────────────────────────────────────────────── +# These are the EdgeX REST API ports. Bind only the ones you need externally. +# Internal MQTT (1883) is NOT exposed – it is a localhost-only bus inside the +# container, preventing external interference (hardening). +EXPOSE 59880 59881 59882 59860 59863 59890 59901 + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/edgex-thin-client/entrypoint.sh b/edgex-thin-client/entrypoint.sh new file mode 100755 index 0000000..a6fabb4 --- /dev/null +++ b/edgex-thin-client/entrypoint.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# ============================================================================= +# entrypoint.sh – EdgeKit Thin Client container entry point +# +# Responsibilities: +# 1. Initialize the PostgreSQL data directory on first run (if empty). +# 2. Set correct ownership on the persistent data volume. +# 3. Hand off to supervisord which manages all EdgeX processes. +# +# The container should be started as root so that: +# • We can chown /data/postgresql to the postgres user. +# • supervisord can drop privileges per-program (postgres runs as 'postgres'). +# ============================================================================= +set -e + +PGDATA="/data/postgresql" +PGUSER="postgres" + +# ── 1. Ensure the PostgreSQL data directory exists and is owned correctly ───── +mkdir -p "${PGDATA}" +chown -R "${PGUSER}:${PGUSER}" "${PGDATA}" + +# ── 2. Initialize the PostgreSQL cluster on first run ──────────────────────── +if [ ! -f "${PGDATA}/PG_VERSION" ]; then + echo "[entrypoint] Initialising PostgreSQL data directory at ${PGDATA} ..." + su-exec "${PGUSER}" initdb \ + --pgdata="${PGDATA}" \ + --username="${PGUSER}" \ + --encoding=UTF8 \ + --locale=C + + # Allow local connections without password (internal use only) + echo "host all all 127.0.0.1/32 trust" >> "${PGDATA}/pg_hba.conf" + echo "host all all ::1/128 trust" >> "${PGDATA}/pg_hba.conf" + + echo "[entrypoint] PostgreSQL initialised." +fi + +# ── 3. Ensure Mosquitto data directory exists (for optional persistence) ────── +mkdir -p /data/mosquitto +chown mosquitto:mosquitto /data/mosquitto 2>/dev/null || true + +# ── 4. Start supervisord (manages all services) ─────────────────────────────── +echo "[entrypoint] Starting supervisord ..." +exec supervisord -c /etc/supervisord.conf diff --git a/edgex-thin-client/mosquitto.conf b/edgex-thin-client/mosquitto.conf new file mode 100644 index 0000000..11757c4 --- /dev/null +++ b/edgex-thin-client/mosquitto.conf @@ -0,0 +1,27 @@ +# ============================================================================= +# mosquitto.conf – EdgeKit Thin Client internal MQTT message bus +# +# Mosquitto listens on localhost:1883 ONLY. +# This is intentional: the MQTT bus is an internal EdgeX message bus and must +# not be accessible from outside the container (hardening requirement). +# +# Anonymous connections are allowed because this is a single-container +# deployment with no external surface – all clients are EdgeX services running +# inside the same container. +# ============================================================================= + +# Listen on localhost only (internal EdgeX message bus) +listener 1883 127.0.0.1 + +# Allow anonymous connections (all clients are internal EdgeX services) +allow_anonymous true + +# Disable persistent session storage (stateless; edge nodes may restart often) +persistence false + +# Log to stdout so supervisord can capture it +log_dest stdout +log_type error +log_type warning +log_type notice +log_type information diff --git a/edgex-thin-client/supervisord.conf b/edgex-thin-client/supervisord.conf new file mode 100644 index 0000000..908b8bd --- /dev/null +++ b/edgex-thin-client/supervisord.conf @@ -0,0 +1,240 @@ +; ============================================================================= +; supervisord.conf – EdgeKit Thin Client process supervisor +; +; Manages all EdgeX Foundry 4.0 services inside the single container, +; together with the embedded PostgreSQL database and Mosquitto MQTT broker. +; +; Startup order is enforced via the `priority` field and `startretries`/ +; `autorestart` settings – higher-priority processes start first; services +; that depend on them will fail-and-retry until their dependencies are ready. +; +; Priority bands: +; 10 – infrastructure (PostgreSQL, Mosquitto) +; 20 – core-keeper (service registry; depends on DB + MQTT) +; 30 – config boot (core-common-config-bootstrapper; one-shot) +; 40 – core-data, core-metadata (depend on keeper + DB) +; 50 – core-command (depends on core-metadata) +; 60 – support services (notifications, scheduler) +; 70 – device-modbus (depends on core-metadata + core-data) +; ============================================================================= + +[supervisord] +nodaemon=true +user=root +logfile=/dev/null +logfile_maxbytes=0 +loglevel=info +pidfile=/tmp/supervisord.pid + +[unix_http_server] +file=/tmp/supervisor.sock + +[supervisorctl] +serverurl=unix:///tmp/supervisor.sock + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface + +; ── Infrastructure ──────────────────────────────────────────────────────────── + +[program:postgresql] +; Managed by entrypoint.sh (initdb + start); supervisord keeps it alive. +command=/usr/bin/postgres -D /data/postgresql +user=postgres +priority=10 +autostart=true +autorestart=true +startsecs=5 +startretries=3 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:mosquitto] +; Embedded MQTT broker – internal EdgeX message bus (localhost only). +command=/usr/sbin/mosquitto -c /etc/mosquitto/mosquitto.conf +user=mosquitto +priority=10 +autostart=true +autorestart=true +startsecs=3 +startretries=3 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +; ── core-keeper (service registry) ─────────────────────────────────────────── + +[program:core-keeper] +command=/edgex/bin/core-keeper +directory=/edgex/services/core-keeper +user=edgex +priority=20 +autostart=true +autorestart=true +startsecs=5 +startretries=10 +environment= + EDGEX_SECURITY_SECRET_STORE="false", + SERVICE_HOST="localhost", + DATABASE_HOST="localhost", + MESSAGEBUS_TYPE="mqtt", + MESSAGEBUS_PROTOCOL="tcp", + MESSAGEBUS_HOST="localhost", + MESSAGEBUS_PORT="1883", + MESSAGEBUS_AUTHMODE="none", + MESSAGEBUS_SECRETNAME="" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +; ── core-common-config-bootstrapper (one-shot; exits 0 on success) ─────────── + +[program:core-ccb] +command=/edgex/bin/core-common-config-bootstrapper + --registry + -cp=keeper.http://localhost:59890 +directory=/edgex/services/core-ccb +user=edgex +priority=30 +autostart=true +; One-shot service: do not restart after clean exit. +autorestart=unexpected +exitcodes=0 +startretries=10 +startsecs=3 +environment= + EDGEX_SECURITY_SECRET_STORE="false", + ALL_SERVICES_DATABASE_HOST="localhost", + ALL_SERVICES_MESSAGEBUS_HOST="localhost", + ALL_SERVICES_REGISTRY_HOST="localhost", + APP_SERVICES_CLIENTS_CORE_METADATA_HOST="localhost", + DEVICE_SERVICES_CLIENTS_CORE_METADATA_HOST="localhost" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +; ── Core Services ───────────────────────────────────────────────────────────── + +[program:core-data] +command=/edgex/bin/core-data + --registry + -cp=keeper.http://localhost:59890 +directory=/edgex/services/core-data +user=edgex +priority=40 +autostart=true +autorestart=true +startsecs=5 +startretries=10 +environment= + EDGEX_SECURITY_SECRET_STORE="false", + SERVICE_HOST="localhost" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:core-metadata] +command=/edgex/bin/core-metadata + --registry + -cp=keeper.http://localhost:59890 +directory=/edgex/services/core-metadata +user=edgex +priority=40 +autostart=true +autorestart=true +startsecs=5 +startretries=10 +environment= + EDGEX_SECURITY_SECRET_STORE="false", + SERVICE_HOST="localhost" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:core-command] +command=/edgex/bin/core-command + --registry + -cp=keeper.http://localhost:59890 +directory=/edgex/services/core-command +user=edgex +priority=50 +autostart=true +autorestart=true +startsecs=5 +startretries=10 +environment= + EDGEX_SECURITY_SECRET_STORE="false", + SERVICE_HOST="localhost", + EXTERNALMQTT_URL="tcp://localhost:1883" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +; ── Supporting Services ─────────────────────────────────────────────────────── + +[program:support-notifications] +command=/edgex/bin/support-notifications + --registry + -cp=keeper.http://localhost:59890 +directory=/edgex/services/support-notifications +user=edgex +priority=60 +autostart=true +autorestart=true +startsecs=5 +startretries=10 +environment= + EDGEX_SECURITY_SECRET_STORE="false", + SERVICE_HOST="localhost" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:support-scheduler] +command=/edgex/bin/support-scheduler + --registry + -cp=keeper.http://localhost:59890 +directory=/edgex/services/support-scheduler +user=edgex +priority=60 +autostart=true +autorestart=true +startsecs=5 +startretries=10 +environment= + EDGEX_SECURITY_SECRET_STORE="false", + SERVICE_HOST="localhost" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +; ── Device Services ─────────────────────────────────────────────────────────── + +[program:device-modbus] +command=/edgex/bin/device-modbus + -cp=keeper.http://localhost:59890 + --registry +directory=/edgex/services/device-modbus +user=edgex +priority=70 +autostart=true +autorestart=true +startsecs=5 +startretries=10 +environment= + EDGEX_SECURITY_SECRET_STORE="false", + SERVICE_HOST="localhost" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/fleet.yaml b/fleet.yaml new file mode 100644 index 0000000..60f1f9d --- /dev/null +++ b/fleet.yaml @@ -0,0 +1,47 @@ +# ============================================================================= +# Rancher Fleet – EdgeKit multi-cluster GitOps +# +# Clusters with label edgekit/role=central receive the MQTT broker (server). +# Clusters with label edgekit/role=edge receive the full k3s edge agent. +# Clusters with label edgekit/role=edge-thin receive the thin edge agent. +# +# See: https://fleet.rancher.io/ref-fleet-yaml +# ============================================================================= + +namespace: edgekit + +helm: + chart: helm/edgekit + releaseName: edgekit + values: + global: + imageRegistry: "" + +targets: + + # Central server – runs the MQTT broker; no edge agent + - name: central + clusterSelector: + matchLabels: + edgekit/role: central + helm: + valuesFiles: + - helm/edgekit/values-server.yaml + + # Edge nodes – run the edge agent; broker is hosted centrally + - name: edge + clusterSelector: + matchLabels: + edgekit/role: edge + helm: + valuesFiles: + - helm/edgekit/values-client.yaml + + # Thin edge nodes – single-container EdgeX; no k3s orchestration needed + - name: edge-thin + clusterSelector: + matchLabels: + edgekit/role: edge-thin + helm: + valuesFiles: + - helm/edgekit/values-thin-client.yaml diff --git a/helm/edgekit/values-client.yaml b/helm/edgekit/values-client.yaml new file mode 100644 index 0000000..2aa92dd --- /dev/null +++ b/helm/edgekit/values-client.yaml @@ -0,0 +1,32 @@ +# ============================================================================= +# EdgeKit Helm – Client (edge node) values override +# +# Apply with: +# helm install edgekit ./helm/edgekit \ +# --namespace edgekit --create-namespace \ +# -f helm/edgekit/values-client.yaml +# +# On an edge cluster the MQTT broker runs remotely (central server), so the +# server component is disabled here. The client edge-agent connects to the +# central broker URL supplied below. +# ============================================================================= + +# Disable the in-cluster MQTT broker – edge nodes connect to the central one +server: + enabled: false + +client: + enabled: true + replicaCount: 1 + + # Point the edge agent at the central MQTT broker + mqttBrokerUrl: "ws://edgekit-server.edgekit.svc.cluster.local:9001" + topicPrefix: "edgekit" + publishIntervalMs: 5000 + + podAnnotations: + edgekit/role: edge + + nodeSelector: {} + tolerations: [] + affinity: {} diff --git a/helm/edgekit/values-server.yaml b/helm/edgekit/values-server.yaml new file mode 100644 index 0000000..168dc2b --- /dev/null +++ b/helm/edgekit/values-server.yaml @@ -0,0 +1,47 @@ +# ============================================================================= +# EdgeKit Helm – Server (central node) values override +# +# Apply with: +# helm install edgekit ./helm/edgekit \ +# --namespace edgekit --create-namespace \ +# -f helm/edgekit/values-server.yaml +# +# On the central server the MQTT broker is enabled and exposed so that edge +# agents can connect. The client agent is disabled – the server does not +# collect metrics itself. +# ============================================================================= + +# Enable the MQTT broker on the central server +server: + enabled: true + replicaCount: 1 + + service: + type: LoadBalancer # expose externally so edge nodes can connect + mqttPort: 1883 + wsPort: 9001 + + persistence: + enabled: true + storageClass: "" + accessMode: ReadWriteOnce + size: 5Gi + + resources: + requests: + cpu: 200m + memory: 128Mi + limits: + cpu: 1000m + memory: 512Mi + + podAnnotations: + edgekit/role: central + + nodeSelector: {} + tolerations: [] + affinity: {} + +# Disable the edge agent on the central server +client: + enabled: false diff --git a/helm/edgekit/values-thin-client.yaml b/helm/edgekit/values-thin-client.yaml new file mode 100644 index 0000000..97ba6b6 --- /dev/null +++ b/helm/edgekit/values-thin-client.yaml @@ -0,0 +1,80 @@ +# ============================================================================= +# EdgeKit Helm – Thin Client (edge-thin node) values override +# +# Apply with: +# helm install edgekit ./helm/edgekit \ +# --namespace edgekit --create-namespace \ +# -f helm/edgekit/values-thin-client.yaml +# +# Thin edge nodes run the single-container EdgeX stack (edgekit-thin-client +# image) via a minimal Kubernetes Deployment. The full multi-container k3s +# EdgeX setup is NOT deployed here. +# +# The thin client has the same functional role as the full k3s edge client but +# is deliberately standalone and not extendable with additional services. +# ============================================================================= + +# Disable the in-cluster MQTT broker – edge nodes connect to the central one +server: + enabled: false + +client: + enabled: true + replicaCount: 1 + + # Point the edge agent at the central MQTT broker + mqttBrokerUrl: "ws://edgekit-server.edgekit.svc.cluster.local:9001" + topicPrefix: "edgekit" + publishIntervalMs: 5000 + + podAnnotations: + edgekit/role: edge-thin + + nodeSelector: {} + tolerations: [] + affinity: {} + +# Thin client: deploy the single-container EdgeX image +edgexThinClient: + enabled: true + image: + repository: edgekit-thin-client + tag: latest + pullPolicy: IfNotPresent + + # Resource limits for a constrained edge device + resources: + limits: + memory: 512Mi + cpu: "1000m" + requests: + memory: 256Mi + cpu: "250m" + + # Persistent volume for PostgreSQL data + persistence: + enabled: true + storageClass: "" + size: 2Gi + accessMode: ReadWriteOnce + + # Expose EdgeX REST API ports inside the cluster only (ClusterIP) + service: + type: ClusterIP + ports: + coreKeeper: 59890 + coreData: 59880 + coreMetadata: 59881 + coreCommand: 59882 + supportNotifications: 59860 + supportScheduler: 59863 + deviceModbus: 59901 + + # Hardening: run as root only for entrypoint.sh chown; supervisord drops privileges + securityContext: + runAsUser: 0 + readOnlyRootFilesystem: true + + nodeSelector: {} + tolerations: [] + affinity: {} diff --git a/scripts/start-edgex-client.sh b/scripts/start-edgex-client.sh new file mode 100755 index 0000000..0205b18 --- /dev/null +++ b/scripts/start-edgex-client.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# ============================================================================= +# scripts/start-edgex-client.sh +# Start the EdgeX Foundry 4.0 (no-security) stack on an edge / client node +# ============================================================================= +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +COMPOSE_FILE="${REPO_ROOT}/docker-compose.edgex-client.yml" + +echo "==> Starting EdgeX client stack (edge node)…" +docker compose -f "${COMPOSE_FILE}" up --pull missing -d + +echo "" +echo "✅ EdgeX client stack is running!" +echo "" +echo " core-data: http://localhost:59880/api/v3/ping" +echo " core-metadata: http://localhost:59881/api/v3/ping" +echo " core-command: http://localhost:59882/api/v3/ping" +echo " support-notifications: http://localhost:59860/api/v3/ping" +echo " support-scheduler: http://localhost:59863/api/v3/ping" +echo " device-modbus: http://localhost:59901/api/v3/ping" +echo " core-keeper (registry): http://localhost:59890/api/v3/ping" +echo "" +echo " View logs: docker compose -f docker-compose.edgex-client.yml logs -f" +echo " Stop: ./scripts/stop-edgex-client.sh" diff --git a/scripts/start-edgex-server.sh b/scripts/start-edgex-server.sh new file mode 100755 index 0000000..9d62047 --- /dev/null +++ b/scripts/start-edgex-server.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# ============================================================================= +# scripts/start-edgex-server.sh +# Start the EdgeX Foundry 4.0 Security Services stack on the central server +# ============================================================================= +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +COMPOSE_FILE="${REPO_ROOT}/docker-compose.edgex-server.yml" + +echo "==> Starting EdgeX server stack (security services)…" +docker compose -f "${COMPOSE_FILE}" up --pull missing -d + +echo "" +echo "✅ EdgeX server (security) stack is running!" +echo "" +echo " secret-store (OpenBao): http://localhost:8200/v1/sys/health" +echo " API gateway (nginx): https://localhost:8443" +echo " proxy-auth: http://localhost:59842/api/v3/ping" +echo "" +echo " View logs: docker compose -f docker-compose.edgex-server.yml logs -f" +echo " Stop: ./scripts/stop-edgex-server.sh" diff --git a/scripts/start-edgex-thin-client.sh b/scripts/start-edgex-thin-client.sh new file mode 100755 index 0000000..69a869d --- /dev/null +++ b/scripts/start-edgex-thin-client.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# ============================================================================= +# scripts/start-edgex-thin-client.sh +# +# Builds (if needed) and starts the EdgeKit thin EdgeX client – a single +# hardened container that bundles all EdgeX Core Services, Supporting Services, +# device-modbus, PostgreSQL, and Mosquitto. +# +# Usage: +# ./scripts/start-edgex-thin-client.sh # build + start +# ./scripts/start-edgex-thin-client.sh --no-build # start without rebuilding +# ============================================================================= +set -e + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +COMPOSE_FILE="docker-compose.edgex-thin-client.yml" +IMAGE_NAME="edgekit-thin-client" + +cd "$REPO_ROOT" + +if [ "$1" != "--no-build" ]; then + echo "Building ${IMAGE_NAME} ..." + docker build -t "${IMAGE_NAME}:latest" edgex-thin-client/ +fi + +echo "Starting EdgeKit thin EdgeX client ..." +docker compose -f "${COMPOSE_FILE}" up -d + +echo "" +echo "Thin EdgeX client started. Waiting for services to initialise (~30s) ..." +sleep 30 + +echo "" +echo "Smoke tests (expect HTTP 200):" +for url in \ + "http://localhost:59890/api/v3/ping" \ + "http://localhost:59880/api/v3/ping" \ + "http://localhost:59881/api/v3/ping" \ + "http://localhost:59882/api/v3/ping" \ + "http://localhost:59860/api/v3/ping" \ + "http://localhost:59863/api/v3/ping" \ + "http://localhost:59901/api/v3/ping"; do + status=$(curl -s -o /dev/null -w "%{http_code}" "${url}" || echo "FAIL") + printf " %-50s %s\n" "${url}" "${status}" +done diff --git a/scripts/stop-edgex-client.sh b/scripts/stop-edgex-client.sh new file mode 100755 index 0000000..aa1d260 --- /dev/null +++ b/scripts/stop-edgex-client.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# ============================================================================= +# scripts/stop-edgex-client.sh +# Stop the EdgeX Foundry 4.0 (no-security) stack on an edge / client node +# ============================================================================= +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +COMPOSE_FILE="${REPO_ROOT}/docker-compose.edgex-client.yml" + +echo "==> Stopping EdgeX client stack…" +docker compose -f "${COMPOSE_FILE}" down + +echo "" +echo "✅ EdgeX client stack stopped." +echo " To also remove volumes: docker compose -f docker-compose.edgex-client.yml down -v" diff --git a/scripts/stop-edgex-server.sh b/scripts/stop-edgex-server.sh new file mode 100755 index 0000000..2b98521 --- /dev/null +++ b/scripts/stop-edgex-server.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# ============================================================================= +# scripts/stop-edgex-server.sh +# Stop the EdgeX Foundry 4.0 Security Services stack on the central server +# ============================================================================= +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +COMPOSE_FILE="${REPO_ROOT}/docker-compose.edgex-server.yml" + +echo "==> Stopping EdgeX server stack…" +docker compose -f "${COMPOSE_FILE}" down + +echo "" +echo "✅ EdgeX server stack stopped." +echo " To also remove volumes: docker compose -f docker-compose.edgex-server.yml down -v" diff --git a/scripts/stop-edgex-thin-client.sh b/scripts/stop-edgex-thin-client.sh new file mode 100755 index 0000000..d2e5ccd --- /dev/null +++ b/scripts/stop-edgex-thin-client.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# ============================================================================= +# scripts/stop-edgex-thin-client.sh +# +# Stops the EdgeKit thin EdgeX client container. +# +# Usage: +# ./scripts/stop-edgex-thin-client.sh # stop; keep volume data +# ./scripts/stop-edgex-thin-client.sh --purge # stop + remove volumes +# ============================================================================= +set -e + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +COMPOSE_FILE="docker-compose.edgex-thin-client.yml" + +cd "$REPO_ROOT" + +if [ "$1" = "--purge" ]; then + echo "Stopping EdgeKit thin EdgeX client and removing volumes ..." + docker compose -f "${COMPOSE_FILE}" down --volumes +else + echo "Stopping EdgeKit thin EdgeX client ..." + docker compose -f "${COMPOSE_FILE}" down +fi + +echo "Done."