No description
  • Rust 68.2%
  • Nix 31.8%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
JMARyA bff1d254c6
feat: appliance images with ecinit as PID 1
A whole device image where the Thing is the machine: kernel, not-os's
stage 1 (initrd that mounts a squashfs store), and ecinit as PID 1
supervising ecd and the Thing's helpers. Nix builds it from the same
`edgecore.things` declaration as the NixOS module (`lib.mkAppliance`).

ecinit (bin/ecinit) is a small, separate PID 1, so a crash in ecd can't
panic the kernel. Its job is fixed at build time by ecinit.json:
- mount the API filesystems; set up the hostname, dirs and links
- run tasks and services as one `after` dependency graph, in parallel
  where allowed, inside a signalfd event loop
- restart services with backoff and reap orphans
- grant device nodes to groups (there's no udev)
- kick the hardware watchdog
- on SIGINT/TERM/USR1/USR2/PWR: SIGTERM, then SIGKILL, then reboot(2)

The appliance module adds DHCP (busybox udhcpc + a lease script), NTP,
stage-2 kernel module loading (kmod), numeric identities for capability
helpers, and enforcement of the Things' capability assertions.

nix/tests/vm-ecinit.nix boots the image in QEMU and checks it from the
host: DHCP lease, TD served, a /proc file binding, a things-declared
Thing, a crash-restarted helper, and Ctrl-Alt-Del → clean shutdown. The
squashfs is ~27.5 MB.

docs/appliance.md covers the idea, the NixOS ↔ appliance mapping, and
the current limits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-22 23:51:51 +02:00
bin feat: appliance images with ecinit as PID 1 2026-09-22 23:51:51 +02:00
crates fix: advertise the routed LAN address over mDNS 2026-09-06 23:52:23 +02:00
docs feat: appliance images with ecinit as PID 1 2026-09-22 23:51:51 +02:00
examples feat: make the TD say what consumers need to act on it 2026-09-06 22:17:58 +02:00
nix feat: appliance images with ecinit as PID 1 2026-09-22 23:51:51 +02:00
.gitignore speaker: implement audio-playback via an mpv idle-helper service 2026-07-06 01:43:36 +02:00
Cargo.lock feat: appliance images with ecinit as PID 1 2026-09-22 23:51:51 +02:00
Cargo.toml feat: appliance images with ecinit as PID 1 2026-09-22 23:51:51 +02:00
flake.lock feat: appliance images with ecinit as PID 1 2026-09-22 23:51:51 +02:00
flake.nix feat: appliance images with ecinit as PID 1 2026-09-22 23:51:51 +02:00
README.md feat: appliance images with ecinit as PID 1 2026-09-22 23:51:51 +02:00
renovate.json Add renovate.json 2026-07-25 02:00:33 +00:00
rust-toolchain.toml initial commit 2026-06-19 23:52:00 +02:00

💠 edgecore

A lean, declarative agent that turns a physical device into a discoverable,
composable capability surface — speaking W3C Web of Things.

edgecore is a framework for special devices and a control daemon, ecd,
that exposes a device's capabilities and values. You describe a device once, in a
single declarative TOML manifest; ecd then:

  • generates a conformant WoT Thing Description (TD 1.1),
  • serves a WoT HTTP API plus a small built-in web GUI,
  • routes each interaction to its backing (a command, file, HTTP endpoint, …),
  • announces the device on the LAN over DNS-SD/mDNS (_wot._tcp).

Any WoT consumer can drive the device with zero edgecore-specific knowledge.

edgecore is a subproject of git.hydrar.de/jmarya/myverse.

Quick start

With Nix (flakes enabled):

nix develop          # dev shell with the pinned Rust toolchain
nix build .#ecd      # build the daemon
nix flake check      # build + clippy + rustfmt + tests

Run the daemon against a manifest:

ecd path/to/device.toml      # serve the device
ecd --check device.toml      # validate the manifest and exit
ecd --td    device.toml      # print the generated Thing Description

One process can host several Things (a WoT Servient) — pass multiple manifests
or a directory of them. They share one port; each keeps its own base path, and
GET /things lists every hosted Thing Description:

ecd things.d/                        # every *.toml in the directory
ecd lobby-01.toml door-02.toml       # or an explicit list

Then visit http://<host>:<port><base> for the GUI, or fetch the TD at
<base>/.well-known/wot.

A minimal manifest

[device]
id    = "urn:edgecore:kiosk:lobby-01"
title = "Lobby Kiosk"
class = "kiosk"

[property.brightness]
type    = "integer"
unit    = "percent"
minimum = 0
maximum = 100
source  = { exec = "backlight-get" }
sink    = { exec = "backlight-set {value}" }

[action.reload]
run = { exec = "kiosk-reload {hard}" }
  [action.reload.input.properties.hard]
  type = "boolean"

[expose]
bind = ["http"]
gui  = true
  [expose.http]
  port = 9688
  base = "/things/lobby-01"
  [expose.discovery]
  mdns = true

See the examples/ directory for full manifests:
lobby-01.toml (kiosk),
camera-01.toml (camera),
env-sensor-01.toml (sensor),
light-01.toml (smart light),
speaker-01.toml (speaker).
See docs/manifest.md for the complete schema.

HTTP surface

Route WoT operation
GET /.well-known/wot fetch the Thing Description
GET /properties/{name} read a property (SSE observe with Accept: text/event-stream)
PUT /properties/{name} write a property
POST /actions/{name} invoke an action (JSON body → backing)
GET /events/{name} subscribe to an event (SSE)
ANY /stream/{*rest} authenticated reverse proxy to a local media server (when [stream] is set)
GET / the web GUI (when expose.gui = true)

All routes are mounted under the device's expose.http.base, and are served over
HTTPS when [expose.http.tls] is set.

Documentation

Repository layout

crates/manifest    manifest types · parse · validate
crates/td          Thing Description model · generation
crates/backing     the Backing trait + exec/file/http/const/journal
crates/server      axum WoT HTTP surface + web GUI + auth
crates/discovery   mDNS / DNS-SD advertise + browse
bin/ecd            the daemon
bin/edgectl        fleet CLI/TUI: discover and drive Things
nix/modules        NixOS modules: ecd, kiosk, camera
examples           example manifests

edgectl — the fleet client

edgectl discovers Things on the LAN over mDNS and drives them by a short
handle — multi-Thing from the start, no per-command endpoint juggling.

edgectl discover                     # browse _wot._tcp → list the whole fleet
edgectl get webcam resolution        # read a property (Thing addressed by handle)
edgectl set lobby brightness 70      # write a property
edgectl invoke webcam snapshot '{"output_dir":"/tmp"}'
edgectl watch door state             # live SSE stream
edgectl tui                          # interactive fleet browser

Add --url <servient> to include hosts not on mDNS, --token/$ECD_TOKEN for
auth (applied per each Thing's advertised scheme), and -k to accept the
self-signed certs edge devices serve.

Status

v0 speaks HTTP(S) (optional TLS with an operator or persisted self-signed
cert — no ACME), uses SSE for property observation and events, and a single
device-wide security scheme that also gates an optional authenticated media
reverse proxy ([stream]). Implemented backings: exec, file, http,
const, journal. See docs/architecture.md for the
current state of each crate.