Metadata-Version: 2.4
Name: fleet-man
Version: 0.0.4
Summary: A small python script fleet package
License-Expression: MIT
Project-URL: Homepage, https://github.com/pypa/sampleproject
Project-URL: Issues, https://github.com/pypa/sampleproject/issues
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# fleet_man

A lightweight Python library for running and monitoring scripts on Raspberry Pis from a browser. Each Pi hosts its own control panel with live log streaming. An optional fleet coordinator aggregates all Pis into a single dashboard.

Built on FastAPI + WebSockets. No external message broker. No database. Each Pi is fully autonomous.

---

## install

```bash
pip install 'uvicorn[standard]' fastapi httpx
```

---

## quick start — single Pi

```python
# app.py
import sys
import uvicorn
from pathlib import Path
from process_manager import ProcessManager, ScriptSpec

PY = sys.executable

scripts = {
    "CAN-M":  ScriptSpec("CAN Manager",  [PY, "gc_monitor/run.py"]),
    "backup": ScriptSpec("backup",        ["bash", "jobs/backup.sh"]),
    "server": ScriptSpec("api server",    [PY, "jobs/server.py"]),
}

mgr = ProcessManager(
    name="pi-kitchen",
    scripts=scripts,
    log_dir=Path("/var/log/jobs"),
    port=5001,
)

app = mgr.create_app()

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=5001)
```

Open `http://<pi-ip>:5001` in your browser.

---

## ProcessManager

```python
ProcessManager(
    scripts,                          # dict[str, ScriptSpec]  — required
    name="pi",                        # node name shown in UI
    log_dir=Path("/tmp/pm_logs"),     # where .log files are written
    coordinator_url=None,             # optional fleet coordinator URL
    host=None,                        # LAN IP advertised to coordinator (auto-detected)
    port=8000,                        # port this Pi listens on
)
```

### ScriptSpec

```python
ScriptSpec(label, cmd)
```

| arg | type | description |
|-----|------|-------------|
| `label` | `str` | display name shown in the UI |
| `cmd` | `list[str]` or `callable` | command to run |

`cmd` can be a callable — it is invoked at start time, so it can pull config from disk or environment dynamically:

```python
def build_cmd():
    config = load_config()
    return ["python", "worker.py", "--env", config["env"]]

ScriptSpec("worker", build_cmd)
```

---

## fleet coordinator

The coordinator runs on any central machine (laptop, NAS, another Pi). It polls each registered Pi, tracks online/offline status, and serves a unified fleet dashboard where you can start and stop scripts across all nodes.

```python
# coordinator.py
import uvicorn
from coordinator import create_coordinator_app

app = create_coordinator_app(fleet_name="home-lab")

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=9000)
```

Open `http://<coordinator-ip>:9000`.

### auto-registration

Pass `coordinator_url` to `ProcessManager` and each Pi registers itself on startup and re-registers every 30 seconds — no manual steps needed. Boot order doesn't matter; Pis retry until the coordinator is reachable.

```python
mgr = ProcessManager(
    name="pi-kitchen",
    scripts=scripts,
    coordinator_url="http://192.168.1.10:9000",
    port=5001,
)
```

You'll see confirmation in the Pi's console:

```
[pm] registered with coordinator at http://192.168.1.10:9000
```

If the coordinator is down at boot:

```
[pm] coordinator unreachable at http://192.168.1.10:9000 — will retry
```

Either way the Pi starts normally. The coordinator is purely additive — removing it changes nothing about how the Pi operates.

### manual registration (curl)

If you're not using `coordinator_url`, you can register a node manually:

```bash
curl -X POST http://192.168.1.10:9000/register \
     -H "Content-Type: application/json" \
     -d '{"name":"pi-kitchen","host":"192.168.1.42","port":5001}'
```

---

## log files

Each script writes stdout and stderr to a `.log` file in `log_dir`. For a script with key `CAN-M` the log is written to `<log_dir>/CAN-M.log`. Logs persist across restarts and are also tailable directly:

```bash
tail -f /var/log/jobs/CAN-M.log
```

The last 500 lines are buffered in memory and replayed instantly when you open the log panel in the browser. New lines stream in live via WebSocket.

---

## API reference

Each Pi exposes the following endpoints:

| method | path | description |
|--------|------|-------------|
| GET | `/` | local dashboard UI |
| GET | `/api/status` | JSON status of all scripts |
| POST | `/api/start/<key>` | start a script |
| POST | `/api/stop/<key>` | stop a script |
| GET | `/api/log/<key>` | last 500 lines of log (plain text) |
| WS | `/ws` | status push every 2s + start/stop commands |
| WS | `/ws/<key>` | live log stream for one script |

The coordinator exposes:

| method | path | description |
|--------|------|-------------|
| GET | `/` | fleet dashboard UI |
| POST | `/register` | register or re-register a node |
| GET | `/api/fleet` | full fleet status JSON |
| GET | `/api/nodes` | all node info |
| POST | `/api/<node>/start/<key>` | start a script on a specific node |
| POST | `/api/<node>/stop/<key>` | stop a script on a specific node |
| WS | `/ws` | live fleet status push + forwarded commands |

---

## architecture

```
[ browser ]
    │
    │  WebSocket  (live status + logs)
    │
    ├─────────────────────────┐
    │                         │
[ coordinator :9000 ]   [ Pi dashboard :5001 ]
    │                         │
    │  HTTP poll /api/status   │  asyncio subprocess
    │  every 3s                │  stdout/stderr → WS
    │                         │
    └──────── Pi nodes ────────┘
         pi-kitchen  :5001
         pi-garage   :5001
         pi-shed     :5001
```

Each Pi is standalone. The coordinator is optional. If the coordinator restarts, Pis re-register automatically within 30 seconds.

---

## notes

- Scripts must run in the foreground. Processes that daemonize (fork and detach) will not be tracked correctly.
- State is in-memory. If the Pi's `process_manager` process restarts, running scripts become orphans. Use systemd to keep `process_manager` itself alive if uptime matters.
- `sys.executable` is recommended over `"python"` or `"python3"` to ensure the correct interpreter is used, especially inside a virtualenv.
