Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24
487 changes: 260 additions & 227 deletions CHANGELOG.md

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# MDK

[![Release](https://img.shields.io/github/v/release/tetherto/mdk?display_name=tag&style=flat-square)](https://github.com/tetherto/mdk/releases/tag/v0.2.0)
[![Release](https://img.shields.io/github/v/release/tetherto/mdk?display_name=tag&style=flat-square)](https://github.com/tetherto/mdk/releases/tag/v0.3.0)
[![MDK UI CI](https://img.shields.io/github/actions/workflow/status/tetherto/mdk/ui.yaml?branch=main&label=UI%20CI&style=flat-square&logo=github)](https://github.com/tetherto/mdk/actions/workflows/ui.yaml)
[![MDK Core CI](https://img.shields.io/github/actions/workflow/status/tetherto/mdk/core.yaml?branch=main&label=Core%20CI&style=flat-square&logo=github)](https://github.com/tetherto/mdk/actions/workflows/core.yaml)
[![CodeQL](https://github.com/tetherto/mdk/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/tetherto/mdk/actions/workflows/github-code-scanning/codeql)
Expand All @@ -12,7 +12,7 @@

MDK is under active development and is **not yet considered stable**.

Current release: [v0.2.0](https://github.com/tetherto/mdk/releases/tag/v0.2.0).
Current release: [v0.3.0](https://github.com/tetherto/mdk/releases/tag/v0.3.0).

## Table of Contents

Expand Down Expand Up @@ -130,10 +130,10 @@ leverage the packages provided for React (Vue, Svelte, and Web Components on the

## Releases

The latest development code is available on the [`main`](https://github.com/tetherto/mdk/tree/main) branch. MDK follows [Semantic Versioning 2.0.0](https://semver.org/): `0.y.z` versions are initial development (public API not stable until `1.0.0`).
The latest development code is available on the [`main`](https://github.com/tetherto/mdk/tree/main) branch. MDK follows [Semantic Versioning 2.0.0](https://semver.org/): `0.y.z` versions are initial development (public API not stable until `1.0.0`); `1.0.0` and above denote a stable public API.

- [`/docs/reference/release-notes/`](./docs/reference/release-notes)
- Full [`CHANGELOG.md`](./CHANGELOG.md)
Releases have notes [`docs/reference/release-notes/](docs/reference/release-notes/) and the full version history is
detailed in the [`CHANGELOG.md`](CHANGELOG.md).

## Get started

Expand Down
166 changes: 106 additions & 60 deletions backend/core/app-node/README.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,28 @@
# @tetherto/mdk-app-node
# App Node

The **App Node** is the mandatory gateway between the client-facing world (UI, AI agents) and the ORK kernel. It is a Fastify-based HTTP/WebSocket server that handles authentication, RBAC, fleet aggregation, and MCP endpoint exposure.
## Overview

UI and AI agents never connect to ORK directly. All traffic flows through the App Node.
[App Node](../../../docs/concepts/stack/app-node.md), `@tetherto/mdk-app-node`, wraps [`@tetherto/mdk-client`](../client/README.md)
and delivers an authenticated HTTP, WebSocket, and MCP interface for consumers that need those capabilities.
It handles authentication, RBAC, fleet aggregation, and MCP endpoint exposure on top of the [ORK kernel](../ork/README.md).
For use cases that do not need the App Node's HTTP surface, RBAC, or plugin system, see
[Use an alternative gateway](../../../docs/concepts/stack/app-node.md#use-an-alternative-gateway).

## Quick Start
> [!TIP]
> New to the App Node? Read the [App Node concept page](../../../docs/concepts/stack/app-node.md).
> Ready to run it? Follow the [run guide](../../../docs/how-to/app-node/run.md).

The easiest way to start the App Node is through `@tetherto/mdk`:
> [!NOTE]
> The App Node connects to ORK via [`@tetherto/mdk-client`](../client/README.md). While
> `startAppNode()` currently accepts one ORK endpoint — `ork`, `orkKey`, or `orkIpc`,
> multi-site aggregation (a single App Node fronting several per-site ORK kernels via `mdk-client`) is
> on the roadmap.

```js
const { getOrk, startAppNode } = require('@tetherto/mdk')

const ork = await getOrk()
const server = await startAppNode({ ork, port: 3000, noAuth: true })
// HTTP server is up at http://localhost:3000
```

For production (with OAuth2):
```js
const server = await startAppNode({
ork,
port: 3000,
auth: {
h0: { method: 'google', credentials: { client: { id: '...', secret: '...' } }, users: ['admin@example.com'] }
}
})
```

## Responsibilities

- **Authentication:** OAuth2 via Google / Microsoft. JWT validation on all routes. Session management.
- **RBAC:** Role-based access control guards all ORK operations.
- **Fleet Aggregation:** Queries multiple workers via ORK and merges responses (site hashrate, average temperature, cross-rack efficiency).
- **ORK Proxy:** Forwards device commands and telemetry queries to ORK via `@tetherto/mdk-client` (IPC by default).
- **WebSocket:** Real-time telemetry subscriptions for the UI.
- **MCP Endpoint:** AI agent integration via the Model Context Protocol.
## HTTP API overview

## HTTP API Overview
The App Node exposes two categories of REST routes:

The App Node exposes REST routes for:
**Core ORK-proxy routes** — hardcoded in `workers/lib/server/routes/`, always present:

| Category | Endpoints |
|----------|-----------|
Expand All @@ -51,13 +35,24 @@ The App Node exposes REST routes for:
| Stats | Aggregated site statistics |
| Health | Worker health status |

## WebSocket
**Plugin routes** — loaded through the plugin system, extendable via `extraPluginDirs`:

| Plugin | Routes |
|--------|--------|
| `auth` | `/auth/userinfo`, `/auth/token`, `/auth/permissions`, `/auth/ext-data` |
| `telemetry` | `/auth/metrics/*` (hashrate, consumption, efficiency, temperature, containers) |
| `site-hashrate` | `/api/site/hashrate-history` |

The [plugin reference](../plugins/README.md) lists every default route, its method, auth requirement, and parameters.

## WebSocket subscriptions

Connect to `ws://localhost:3000/ws` for real-time telemetry subscriptions. Authentication is required in production mode.

## Configuration

Config files are written to `opts.root/config/facs/` by `startAppNode()`. Example files ship in `backend/core/app-node/config/facs/*.example`. Edit the generated files to persist your changes across restarts.
Config files are written to `opts.root/config/facs/` by `startAppNode()`. Example files ship in `backend/core/app-node/config/facs/*.example`.
Edit the generated files to persist your changes across restarts.

| File | Controls |
|------|---------|
Expand All @@ -68,26 +63,69 @@ Config files are written to `opts.root/config/facs/` by `startAppNode()`. Exampl
| `net.config.json` | IP assignment (DHCP facility) |
| `logging.config.json` | Log level, format |

## ORK Connection
## ORK connection

Two transports are available. Pass exactly one to `startAppNode()`:

**IPC (default — same host)**

The App Node connects to ORK over IPC (Unix socket) by default. The socket path is configured in `common.json`:
The App Node dials ORK over a Unix socket. The default socket path is `os.tmpdir()/mdk/ork.sock`.

```json
{ "orkIpc": "/tmp/mdk/ork.sock" }
```

Override via `startAppNode({ orkIpc: '/custom/path.sock' })` or pass `orkIpc: false` to disable the MDK client (useful when testing without a live ORK).
Override via `startAppNode({ orkIpc: '/custom/path.sock' })` or pass `orkIpc: false` to disable the MDK client
(useful when testing without a live ORK).

**HRPC (cross-host)**

When ORK runs on a separate host, pass the ORK HRPC gateway public key. This selects the HRPC transport and disables IPC automatically:

```js
await startAppNode({ orkKey: '<ork-gateway-pubkey-hex>' })
```

Obtain the ORK gateway key with `ork.getPublicKey().toString('hex')` on the host running ORK, then share it with the App Node host.

Pre v1.0, ORK's `auth.whitelist` defaults to empty (any HRPC caller is admitted). When an allowlist is configured, the App Node's DHT
public key must be added before the connection is accepted — see [ORK transports](../ork/README.md#transports).

## Security Model
## Security model

- UI and AI agents authenticate via **JWT Bearer tokens**
- ORK is protected by an HRPC **whitelist** — the App Node's public key must be in ORK's allowed list
- Once whitelisted, ORK trusts all messages from the App Node implicitly; user-level auth is the App Node's responsibility
- AI agents are treated as authenticated clients — they go through the same JWT/RBAC path as human API consumers
- **ORK connection security** depends on the transport:
- **IPC (same host, default)**: implicit trust — no allowlisting needed
- **HRPC (remote ORK)**: ORK maintains an HRPC firewall; the App Node's DHT public key must be in ORK's `auth.whitelist`.
See [ORK Transports](../ork/README.md#transports) and the [`auth-whitelist` example](../../../examples/backend/ork/auth-whitelist.js)
for the key exchange pattern.
- Once connected, ORK trusts all messages from the App Node implicitly; user-level auth is the App Node's responsibility
- AI agents are treated as authenticated clients, going through the same JWT/RBAC path as human API consumers

## Extending the App Node
## Extend the App Node

Add custom routes by passing `additionalRoutes` to `startAppNode()`:
### Plugin system (recommended)

Pass plugin directories via `extraPluginDirs` to load additional routes at startup alongside the default plugins:

```js
await startAppNode({
ork,
extraPluginDirs: [
path.join(__dirname, 'plugins/my-metrics')
]
})
```

Plugins receive `(req, services)` in every controller, where `services.mdkClient` and `services.dataProxy` give access to ORK
and historical data without any protocol knowledge. The default plugins (`auth`, `telemetry`, `site-hashrate`) are loaded the same way.

The [plugin authoring guide](../../../docs/how-to/app-node/plugins.md) and the [plugin reference](../plugins/README.md) cover the full manifest schema, controller
contract, services bag, and loader errors.

### Raw Fastify routes

For one-off handlers that do not need the plugin manifest format, pass `additionalRoutes` directly:

```js
await startAppNode({
Expand All @@ -102,28 +140,36 @@ await startAppNode({
})
```

## Running Standalone
These are registered as plain Fastify routes — no `services` injection, no manifest validation, no auth wiring.

```bash
cd backend/core/app-node
npm install
npm run dev # Development mode on port 3000
npm start # Production mode
```

## Directory Layout
## Directory layout

```
app-node/
├── workers/
│ └── http.node.wrk.js # WrkServerHttp — Fastify worker
│ ├── http.node.wrk.js # WrkServerHttp — Fastify worker, mounts plugins and routes
│ └── lib/
│ ├── plugin-loader.js # Loads mdk-plugin.json manifests, validates structure
│ ├── plugin-adapter.js # Converts plugin routes to Fastify handlers, wires auth/cache
│ ├── auth.js # JWT validation, OAuth2 callbacks
│ ├── data.proxy.js # Historical data aggregation via worker tail-logs
│ └── server/ # Raw Fastify route definitions (non-plugin routes)
├── config/
│ └── facs/ # Example config files (*.json.example)
├── db/ # SQLite database files
├── store/ # Hyperbee storage
│ └── facs/ # Example config files (*.json.example)
├── db/ # SQLite database files
├── store/ # Hyperbee storage
└── tests/
├── unit/
└── integration/
├── api.test.js # HTTP route tests
└── ws.test.js # WebSocket tests
├── api.test.js # HTTP route tests
└── ws.test.js # WebSocket tests
```

## Next steps

- Understand the [App Node as a development surface](../../../docs/concepts/stack/app-node.md)
- [Run the App Node](../../../docs/how-to/app-node/run.md)
- [Add routes with the plugin system](../../../docs/how-to/app-node/plugins.md)
- Browse the [default plugin route reference](../plugins/README.md)
- See a complete [worked example](../../../examples/full-site/README.md)
- Browse the [`startAppNode()` options](../mdk/README.md)
Loading
Loading