docs: run and query a node

This commit is contained in:
Igor Sirotin
2026-05-19 17:58:39 +01:00
parent 28cd24c5c1
commit ac342647dc
8 changed files with 130 additions and 366 deletions
+14
View File
@@ -0,0 +1,14 @@
services:
logos-node:
build:
context: https://github.com/logos-co/logos-docker.git#611a7282926724c1cce8119f07fd7a68b5a9bccd
image: logos-node:dev-local
container_name: logos-node
volumes:
- ./conf:/conf:ro
ports:
- "30303:30303/tcp" # p2p
- "9000:9000/udp" # discv5
# The image starts the logoscore daemon (`logoscore -D -m ./modules`).
# Loading the module and starting the node is a one-shot step run from
# the host after `compose up` — see docs/run-node.md.
-41
View File
@@ -1,41 +0,0 @@
# Local-dev variant of logos-co/logos-docker's Dockerfile.
#
# Difference: builds `delivery_module` from source instead of installing the
# `build-20260422-...` release via lgpd+lgpm. That release was compiled
# against an older logos-cpp-sdk than current `logoscore` HEAD, so RPC replies
# of type `LogosResult` arrive at the daemon as an unknown metatype and get
# serialized as JSON null. Building both from HEAD pins the same
# logos-cpp-sdk revision (verified via flake.lock in both repos), so the
# `LogosResult` ABI matches.
# Stage 1: Build
FROM nixos/nix:2.34.1 AS builder
RUN echo "experimental-features = nix-command flakes" >> /etc/nix/nix.conf
WORKDIR /app
RUN nix build 'github:logos-co/logos-logoscore-cli#cli-appimage' --out-link ./logoscore --refresh
RUN nix build 'github:logos-co/logos-delivery-module#install-portable' --out-link ./delivery --refresh
RUN mkdir -p /app-final/logos /app-final/modules \
&& cp -rL ./logoscore/* /app-final/logos/ \
&& cp -rL ./delivery/modules/delivery_module /app-final/modules/delivery_module
# Stage 2: Runtime
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl netcat-openbsd && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app-final/logos /app/logos
COPY --from=builder /app-final/modules /home/ubuntu/modules
RUN cd /app/logos && chmod a+rx logoscore.AppImage \
&& ./logoscore.AppImage --appimage-extract > /dev/null \
&& mv squashfs-root logoscore \
&& rm logoscore.AppImage \
&& ln -s /app/logos/logoscore/AppRun /bin/logoscore
RUN chown -R ubuntu:ubuntu /home/ubuntu
USER ubuntu
WORKDIR /home/ubuntu
CMD ["logoscore", "-D", "-m", "/home/ubuntu/modules"]
-14
View File
@@ -1,14 +0,0 @@
services:
logos-node:
build: .
image: logos-node:dev-local
container_name: logos-node
volumes:
- ./conf:/conf:ro
ports:
- "30303:30303/tcp" # p2p
- "9000:9000/udp" # discv5
# CMD inherited from logos-docker Dockerfile:
# logoscore -D -m /home/ubuntu/modules
# Bootstrap (load + createNode + start) is one-shot — run from the host
# via `docker exec` after `compose up`. See run-locally.md.
-171
View File
@@ -1,171 +0,0 @@
# Infra dashboard — collecting node info via `logoscore`
Reference for the [Logos fleet dashboard](https://github.com/status-im/infra-logos/issues/4): how to scrape node identity, version, and Prometheus metrics from a Logos node that runs as `logoscore` + `delivery_module` in a Docker container. **No REST API is exposed** — all data comes through the `logoscore` CLI, which talks to the daemon over a local Unix socket inside the container.
Companion to [`run-locally.md`](./run-locally.md) (covers the build/run pipeline this doc assumes).
## Required change to the infra role
The current [`infra-role-logos-node`](https://github.com/status-im/infra-role-logos-node) entrypoint runs `logoscore` in **inline mode** (`--load-modules X -c module.method(...)`). There is no daemon socket in that mode, so `logoscore call ...` from `docker exec` cannot work. The role currently works around this by grepping `docker logs` for the peer ID / ENR / mix key (`tasks/delivery/query.yml` — every step is marked `FIXME: Use API when available!`).
**To enable API-driven queries, switch the entrypoint to daemon mode.** Replace `templates/entrypoint.sh.j2` with:
```bash
#!/usr/bin/env bash
set -e
# Start the daemon in the background
./logos/bin/logoscore -D -m ./modules &
DAEMON_PID=$!
# Wait for the local socket to come up, then load + start the modules
until ./logos/bin/logoscore status --json >/dev/null 2>&1; do sleep 1; done
{% for m in logos_node_modules %}
./logos/bin/logoscore load-module {{ m }}
{% endfor %}
{% for cmd in logos_node_module_commands %}
./logos/bin/logoscore call {{ cmd | replace('.', ' ', 1) | replace('(', ' ') | replace(')', '') }}
{% endfor %}
# Hand PID 1 over to the daemon so docker stop works
wait $DAEMON_PID
```
(The `-c module.method(args)``call module method args` rewrite is a literal string transform; you may prefer to express the commands directly as `logoscore call` lines in the role's defaults.)
With that change, `~/.logoscore/{daemon,client}` exists inside the container and `docker exec logos-node /app/logos/bin/logoscore ...` works for everything below.
## Outputs needed by the dashboard
| Dashboard field | Command (run inside the container) | Source / notes |
|------------------------|---------------------------------------------------------------------|----------------|
| Logos Core version | `logoscore status --json``.daemon.version` | Version of the `logoscore` binary itself. |
| Loaded modules | `logoscore status --json``.modules[]` | Each entry: `name`, `status` (`loaded` / `not_loaded` / `crashed`). Per-module version is **not** in the status payload today — see "Known gaps". |
| Delivery node version | `logoscore call delivery_module getNodeInfo Version --json` | Returns `liblogosdelivery` git version. May currently return `"n/a"` — see "Known gaps". |
| Peer ID | `logoscore call delivery_module getNodeInfo MyPeerId --json` | libp2p peer id. Replaces the `docker logs | grep listenAddresses` hack. |
| ENR | `logoscore call delivery_module getNodeInfo MyENR --json` | discv5 ENR URI. Replaces the `docker logs | grep 'discoverable ENR'` hack. |
| Multiaddresses | `logoscore call delivery_module getNodeInfo MyMultiaddresses --json` | Comma-separated listen addrs. |
| Prometheus metrics | `logoscore call delivery_module getNodeInfo Metrics --json` | Full Prometheus text exposition. Includes `libp2p_peers` (connected peer count) and all waku/libp2p counters. |
`getAvailableNodeInfoIDs` returns the canonical list at runtime — currently `[Version, Metrics, MyMultiaddresses, MyENR, MyPeerId]`.
## Response shape
Every `logoscore --json` response wraps the value:
```json
{ "status": "ok",
"module": "delivery_module",
"method": "getNodeInfo",
"result": { "success": true, "error": null, "value": "<payload>" } }
```
The actual value lives at `.result.value`. Errors surface as `.status == "error"` (CLI-level) or `.result.success == false` (module-level).
## Drop-in replacement for `tasks/delivery/query.yml`
```yaml
---
- name: Get peer ID from delivery_module
shell: >
docker exec {{ logos_node_cont_name }}
/app/logos/bin/logoscore --json call delivery_module getNodeInfo MyPeerId
| jq -r '.result.value'
register: logos_node_peer_id_raw
changed_when: false
failed_when: false
- name: Get ENR from delivery_module
shell: >
docker exec {{ logos_node_cont_name }}
/app/logos/bin/logoscore --json call delivery_module getNodeInfo MyENR
| jq -r '.result.value'
register: logos_node_enr_raw
changed_when: false
failed_when: false
- name: Get listen multiaddresses from delivery_module
shell: >
docker exec {{ logos_node_cont_name }}
/app/logos/bin/logoscore --json call delivery_module getNodeInfo MyMultiaddresses
| jq -r '.result.value'
register: logos_node_multiaddr_raw
changed_when: false
failed_when: false
- name: Set delivery node facts
set_fact:
logos_node_peer_id: '{{ logos_node_peer_id_raw.stdout | default("none", true) }}'
logos_node_enr: '{{ logos_node_enr_raw.stdout | default("none", true) }}'
logos_node_multiaddr: '{{ logos_node_multiaddr_raw.stdout | default("none", true) }}'
```
The mix-public-key extraction has no equivalent `getNodeInfo` ID yet (`Mix` is not in `NodeInfoId` upstream); keep the log-grep for that one until the module adds it.
## Scrape script for the fleet dashboard
The dashboard collector (Python script in `infra-sites/ansible/roles/fleets-dash-*`) can hit each node's docker daemon over SSH and run:
```bash
#!/usr/bin/env bash
set -euo pipefail
EXEC="docker exec logos-node /app/logos/bin/logoscore --json"
core_version=$($EXEC status | jq -r '.daemon.version')
modules=$( $EXEC status | jq -c '.modules')
node_version=$($EXEC call delivery_module getNodeInfo Version | jq -r '.result.value')
peer_id=$( $EXEC call delivery_module getNodeInfo MyPeerId | jq -r '.result.value')
enr=$( $EXEC call delivery_module getNodeInfo MyENR | jq -r '.result.value')
maddrs=$( $EXEC call delivery_module getNodeInfo MyMultiaddresses | jq -r '.result.value')
# Prometheus exposition — write to a textfile collector for node_exporter to pick up
$EXEC call delivery_module getNodeInfo Metrics | jq -r '.result.value' \
> /var/lib/node_exporter/textfile/delivery.prom
```
Same shape the existing fleet-dash collectors (`fleets-dash-waku`, `fleets-dash-nimbus`) use — they produce a per-node JSON blob and render it client-side.
## Remote scraping (alternative to `docker exec`)
If the collector cannot SSH+exec into each host, expose the daemon's RPC over TCP+TLS instead:
1. Add a TLS listener to the entrypoint:
```bash
./logos/bin/logoscore -D -m ./modules \
--module-transport core_service=tcp_ssl,host=0.0.0.0,port=6443,cert=/conf/cert.pem,key=/conf/key.pem
```
2. Expose the port in `docker-compose.yml`:
```yaml
ports:
- "6443:6443/tcp"
```
3. Issue a token on each node and copy it to the collector host:
```bash
docker exec logos-node /app/logos/bin/logoscore issue-token --name dashboard
# → /root/.logoscore/daemon/tokens/dashboard.json inside the container
```
4. On the collector, dial the node:
```bash
LOGOSCORE_TOKEN=$(jq -r .token /etc/logoscore/dashboard.json) \
LOGOSCORE_CLIENT_TCP_HOST=node.fleet.logos.co \
LOGOSCORE_CLIENT_TCP_PORT=6443 \
logoscore --json call delivery_module getNodeInfo MyPeerId
```
`docker exec` is simpler if the collector already has SSH to each host; TCP+TLS is needed if you want a single collector with no SSH path to the fleet.
## Known gaps
- **Per-module versions** are not exposed by `logoscore status` / `list-modules`. The manifest knows the version (`manifest.json["version"]`), and each plugin implements `version()`, but neither is routed to the CLI today. Workaround: read the manifest directly inside the container (`docker exec logos-node cat /app/modules/delivery_module/manifest.json | jq -r .version`) or pull from `getNodeInfo Version` for the delivery node version specifically.
- **`getNodeInfo Version` may return `"n/a"`** when `liblogosdelivery` was built without git version info baked in. Tracked upstream — treat it as best-effort.
- **No Prometheus HTTP endpoint.** Logos Core does not host an HTTP server. The dashboard must pull metrics via `getNodeInfo Metrics` and re-expose them (textfile collector / push gateway / scrape-by-proxy). Background: [logos-liblogos#118](https://github.com/logos-co/logos-liblogos/issues/118), [logos-liblogos#120](https://github.com/logos-co/logos-liblogos/issues/120).
- **Mix public key** has no `getNodeInfo` ID upstream yet — keep the existing log-grep until it's added.
+65
View File
@@ -0,0 +1,65 @@
# Query a running node
A delivery node exposes no HTTP/REST API. All node info — identity, version,
and Prometheus metrics — is read through the `logoscore` CLI talking to the
daemon. Assumes a node started per [`run-node.md`](./run-node.md).
Run the commands inside the container (`docker exec logos-node ...`) or
directly if you have `logoscore` on the host.
## List available info
```bash
logoscore call delivery_module getAvailableNodeInfoIDs --json | jq
```
```json
{
"method": "getAvailableNodeInfoIDs",
"module": "delivery_module",
"result": {
"error": null,
"success": true,
"value": "@[Version, Metrics, MyMultiaddresses, MyENR, MyPeerId]"
},
"status": "ok"
}
```
## Read a specific value
```bash
logoscore call delivery_module getNodeInfo MyENR --json | jq
```
```json
{
"method": "getNodeInfo",
"module": "delivery_module",
"result": {
"error": null,
"success": true,
"value": "enr:-LW4QItc5tHj3rWoFaaQIUWvaBYijDf2TJKW83SNdyylJVAYVoUlBl1h5..."
},
"status": "ok"
}
```
The value lives at `.result.value`. Extract it with `jq -r`:
```bash
logoscore call delivery_module getNodeInfo MyPeerId --json | jq -r '.result.value'
```
## Info IDs
| ID | Command | Returns |
| ------------------ | ------------------------------ | ----------------------------- |
| `MyPeerId` | `getNodeInfo MyPeerId` | libp2p peer ID |
| `MyENR` | `getNodeInfo MyENR` | discv5 ENR URI |
| `MyMultiaddresses` | `getNodeInfo MyMultiaddresses` | Listen multiaddresses |
| `Metrics` | `getNodeInfo Metrics` | Prometheus text exposition |
| `Version` | `getNodeInfo Version` | `liblogosdelivery` version |
The `logoscore` binary version is in `logoscore status --json`
`.daemon.version`.
-140
View File
@@ -1,140 +0,0 @@
# Run the delivery node locally
Headless delivery node = `logoscore` daemon + `delivery_module`. No GUI, no HTTP server, all interaction via the `logoscore` CLI client.
## Prerequisites
- Nix with flakes enabled (`experimental-features = nix-command flakes`)
- macOS (aarch64/x86_64) or Linux (aarch64/x86_64)
## Build
1. Build the delivery module into a runnable layout:
```bash
git clone https://github.com/logos-co/logos-delivery-module.git
cd logos-delivery-module
nix build '.#install'
```
Produces `./result/modules/delivery_module/{delivery_module_plugin.dylib, manifest.json, ...}` — a directory tree shaped like a modules registry, which is what `logoscore -m` expects.
2. Build the `logoscore` CLI (daemon + client):
```bash
git clone https://github.com/logos-co/logos-logoscore-cli.git
cd logos-logoscore-cli
nix build
```
Produces `./result/bin/logoscore`.
## Run — daemon mode (recommended for the dashboard workflow)
1. Start the daemon, pointing it at the modules dir produced above:
```bash
./result/bin/logoscore -D -m /path/to/logos-delivery-module/result/modules
```
2. From another terminal, sanity check:
```bash
./result/bin/logoscore status --json
./result/bin/logoscore list-modules --json
```
On first start the daemon writes `~/.logoscore/{daemon,client}/` and auto-issues a local-only auth token, so same-host client commands work without extra setup.
## Boot the delivery node
1. Load the plugin:
```bash
logoscore load-module delivery_module --json
```
2. Write a minimal config (`Edge` mode on the `logos.dev` preset is enough for read-only queries):
```json
{"logLevel":"INFO","mode":"Edge","preset":"logos.dev"}
```
Save as `node.json`.
3. Create and start the node:
```bash
logoscore call delivery_module createNode @node.json --json
logoscore call delivery_module start --json
```
The `@file` syntax reads the parameter from a file — required for `createNode` because the JSON config contains characters the shell would otherwise eat.
Other `createNode` keys (cluster, tcpPort, entryNodes, etc.) are documented in `src/delivery_module_plugin.h` and `README.md`. Use `mode: "Core"` for relay-participating nodes; `Edge` is sufficient when you only need observability.
## Run — inline mode (current infra deployment)
`infra-role-logos-node`'s entrypoint runs:
```bash
./logos/bin/logoscore \
-m ./modules \
--load-modules delivery_module \
-c 'delivery_module.createNode(@/conf/waku_config.json)' \
-c 'delivery_module.start()'
```
That's **inline mode** — a single process that runs the `-c` calls then stays alive. No daemon socket is opened, so `logoscore call ...` from another shell will fail. This is why the current `tasks/delivery/query.yml` resorts to grepping `docker logs`.
To unblock dashboard scraping (see [`infra-dashboard.md`](./infra-dashboard.md)), add `-D` to the entrypoint and drop `--load-modules` / `-c` (load + start happen as client calls after boot). See "Docker deployment" below.
## Docker deployment
Two paths, depending on what you're verifying.
### Production image (Linux amd64 hosts)
Source: [`status-im/infra-role-logos-node`](https://github.com/status-im/infra-role-logos-node) → image `harbor.status.im/logos-co/logos-node:deploy-logos-dev` deployed via `docker-compose.yml`. The image's stock `CMD` is already `./logos/bin/logoscore -D -m ./modules` — daemon mode is the default. The infra role currently *overrides* this with an inline-mode entrypoint; for the dashboard workflow, don't override it.
> **Apple silicon caveat (verified 2026-05-14):** this image is published only for `linux/amd64`. On Apple silicon (`linux/arm64/v8`), Docker Desktop pulls it anyway and emits
>
> > `the requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested`
>
> then runs it under Rosetta emulation. Under that emulation `load-module` crashes with `boost::asio: Bad file descriptor` (reproduces with the stock image and zero overrides). The image works fine on native amd64 Linux. For local dev on Apple silicon, use the "Build natively" path below.
To reproduce on amd64 Linux: replace the `build: .` line in [`docker-compose.yml`](./docker-compose.yml) with `image: harbor.status.im/logos-co/logos-node:deploy-logos-dev`, drop the `Dockerfile`, then follow steps 23 below.
### Build natively (works on macOS / Apple silicon)
[`docker-compose.yml`](./docker-compose.yml) + [`Dockerfile`](./Dockerfile) adapt [`logos-co/logos-docker`](https://github.com/logos-co/logos-docker) to build both `logoscore` and `delivery_module` from source in the same image, instead of installing the prebuilt `delivery_module` release via `lgpd`+`lgpm`. That release was compiled against an older `logos-cpp-sdk` than current `logoscore` HEAD — the `LogosResult` ABI doesn't match, so every `call`'s reply gets serialized as JSON `null` (`status: ok, result: null`) even though the underlying method succeeds. Both `github:logos-co/logos-logoscore-cli` and `github:logos-co/logos-delivery-module` HEAD pin the same `logos-cpp-sdk` revision, so building both fixes the mismatch.
1. Bring it up (first build runs Nix + AppImage extraction; allow ~3045 min):
```bash
docker compose up -d --build
docker exec logos-node logoscore status --json
```
2. Bootstrap the delivery node (one-shot, from the host):
```bash
docker exec logos-node logoscore load-module delivery_module --json
docker exec logos-node logoscore call delivery_module createNode @/conf/logos-dev.json --json
docker exec logos-node logoscore call delivery_module start --json
```
## Shut down
```bash
logoscore call delivery_module stop --json
logoscore unload-module delivery_module
logoscore stop # stops the daemon
```
## Troubleshooting
- **`status` says `not_running` after `-D`** — the daemon writes its log to stdout; redirect it (`logoscore -D -m ... > daemon.log 2>&1 &`) and tail the file. Look for `Module loaded:` and `Logoscore daemon started`.
- **`load-module` fails with `MODULE_NOT_FOUND`** — `-m` points at a directory that contains module subdirs, not at a single module. Check `ls $MODULES_DIR/delivery_module/manifest.json` resolves.
- **`createNode` hangs** — first call downloads/connects to bootstrap peers; allow ~30s on a cold start.
- **`logoscore call` inside the container says `not_configured` / `not_running`** — the entrypoint is inline mode, not daemon mode. The CLI client needs a daemon to dial; see "Docker deployment" above.
+51
View File
@@ -0,0 +1,51 @@
# Run a delivery node
Runs a delivery node (`logoscore` daemon + `delivery_module`) in Docker.
There is no GUI or HTTP API — interaction is via the `logoscore` CLI.
## Prerequisites
- Docker with Compose
## Start
```bash
git clone https://github.com/logos-co/logos-delivery-module.git
cd logos-delivery-module
docker compose up -d --build
```
First build runs Nix and downloads release packages — allow ~3045 min.
Later starts are fast.
## Boot the node
The daemon is running; load the module and start the node:
```bash
docker exec logos-node logoscore load-module delivery_module --json
docker exec logos-node logoscore call delivery_module createNode @/conf/logos-dev.json --json
docker exec logos-node logoscore call delivery_module start --json
```
Verify:
```bash
docker exec logos-node logoscore status --json
```
The node is now connected to the `logos.dev` network. See
[`query-node.md`](./query-node.md) to read its peer ID, ENR, and metrics.
## Stop
```bash
docker compose down
```
## Configuration
The node config is [`conf/logos-dev.json`](../conf/logos-dev.json), mounted
into the container at `/conf`. It uses the `logos.dev` network preset. Edit it
and re-run the boot steps to change settings; available keys are documented in
the [README](../README.md#node-configuration-createnode).