# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

`rhubarbe` is an asyncio-based Python package for managing the [R2Lab testbed](https://r2lab.inria.fr) at Inria Sophia Antipolis. It controls testbed nodes through their CMC (Chassis Management Controller) cards, handles OS image loading/saving via `frisbee`, and runs monitoring daemons that feed a sidecar websocket server.

## Development Commands

### Install for development
```bash
pip install -e .
```

### Linting
```bash
make pep8      # runs flake8 (max line length 80)
make pylint    # runs pylint on all tracked .py files
```

### Deploying to test/production
```bash
make preplab   # rsync to preplab.pl.sophia.inria.fr for testing
make r2lab     # rsync to faraday.inria.fr (production)
# then on the remote host:
# conda activate r2lab-dev-314 && pip install -e /tmp/r2lab-dev-rhubarbe
```

There are no automated tests in this repository (`tests/` does not exist). Testing is done manually on the real testbed.

## Architecture

### Entry Point

All commands share a single entry point (`rhubarbe/__main__.py:main`). The binary name determines the subcommand: `rhubarbe-load` calls the `load` function, while `rhubarbe load` passes `load` as the first argument. New subcommands must be registered with the `@subcommand` decorator in `rhubarbe/main.py`.

### Core Data Flow

1. **Selector** (`selector.py`) — parses node range specs like `1-5`, `~10-20`, `1-18-2` into a set of node indices.
2. **InventoryNodes** (`inventorynodes.py`) — singleton that reads `/etc/rhubarbe/inventory-nodes.json` (or the path from config) to map node names to MAC/IP addresses.
3. **Config** (`config.py`) — singleton that layers config files: bundled default → `/etc/rhubarbe/rhubarbe.conf.local` → `~/.rhubarbe.conf` → `./rhubarbe.conf`. Supports per-hostname keys like `bandwidth.faraday`.
4. **Node** (`node.py`) — handle for a single testbed node, identified by CMC hostname (e.g. `reboot01`). Sends HTTP commands to CMC cards and uses telnet to bootstrap frisbee.
5. **Action** (`action.py`) — sends a CMC verb (on/off/reset/status/…) to a set of nodes via `Node`.

### Image Loading Pipeline (`imageloader.py`)

`rhubarbe load` orchestrates:
1. Reboot all selected nodes into a frisbee PXE image via CMC reset
2. Start `frisbeed` (the multicast image server) — see `frisbeed.py`
3. Connect to each node via telnet (`telnet.py`) and run the `frisbee` client
4. Reset nodes back to normal after completion

### Monitoring Daemons (`rhubarbe/monitor/`)

Long-running services that run with `asyncio.Runner` via `MonitorLoop`:
- `monitornodes` — periodically probes all nodes and pushes status via websocket to the sidecar server
- `monitorphones` — same for phones
- `monitorpdus` — same for PDUs
- `monitorleases` — polls the PLCAPI lease server and reacts to sidecar requests
- `accountsmanager` — syncs Unix accounts with PLCAPI changes

The `MonitorLoop` class (`monitor/loop.py`) wraps `asyncio.Runner` and installs signal handlers for clean shutdown.

### Inventory Files

There are three inventory types, each read as a singleton:
- `InventoryNodes` — from `inventory-nodes.json` (JSON), maps node→MAC/IP
- `InventoryPhones` — from `inventory-phones.json` (JSON)
- `InventoryPdus` — from `inventory-pdus.yaml` (YAML, deserialized with `dataclass_wizard`)
- `InventoryRelays` — from `inventory-relays.yaml` (YAML), for temperature sensors

### Singleton Pattern

`Config`, `InventoryNodes`, `InventoryPhones`, and `InventoryPdus` are all singletons (using `rhubarbe/singleton.py`). Creating them multiple times returns the same instance — be aware when testing or changing config paths.

### Display

Two display backends share the same interface:
- `Display` (`display.py`) — text-based progress output using `progressbar33`
- `DisplayCurses` (`display_curses.py`) — full-terminal curses UI (enabled with `-c`/`--curses`)

Both read from an `asyncio.Queue` message bus fed by the various async tasks.

### PDU Management (`inventorypdus.py`)

PDUs are defined in `/etc/rhubarbe/inventory-pdus.yaml` and deserialized into dataclasses with `dataclass_wizard`. The `rhubarbe pdu` subcommand handles listing, status, on/off/reset of managed power-distribution units. Devices support optional soft-shutdown via SSH before hard power-off.

### Relay API (`rhubarbe/api/relays.py`)

A FastAPI service (`rhubarbe relaysapi`) exposes temperature data from relay devices on port 8000. Temperature data is stored in `/etc/rhubarbe/relays/temperatures.csv` and periodically collected via a systemd timer.

## Key Configuration Sections

Config file format is `.ini`. Notable sections: `testbed`, `nodes`, `networking`, `frisbee`, `sidecar`, `monitor`, `plcapi`. Values can be host-specific: `bandwidth.faraday = 500` overrides `bandwidth` only on the host named `faraday`.

## Adding a New Subcommand

1. Add a function decorated with `@subcommand` in `rhubarbe/main.py`
2. Add the corresponding entry point in `pyproject.toml` under `[project.scripts]`
