cyclo-manager 0.1.1.dev1__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cyclo_manager-0.2.0/PKG-INFO +269 -0
- cyclo_manager-0.2.0/README.md +255 -0
- cyclo_manager-0.2.0/cyclo_host_agent/__init__.py +19 -0
- cyclo_manager-0.2.0/cyclo_host_agent/main.py +56 -0
- cyclo_manager-0.2.0/cyclo_host_agent/models.py +105 -0
- cyclo_manager-0.2.0/cyclo_host_agent/routers/__init__.py +19 -0
- cyclo_manager-0.2.0/cyclo_host_agent/routers/repos.py +415 -0
- cyclo_manager-0.2.0/cyclo_host_agent/routers/update.py +77 -0
- cyclo_manager-0.2.0/cyclo_manager.egg-info/PKG-INFO +269 -0
- {cyclo_manager-0.1.1.dev1 → cyclo_manager-0.2.0}/cyclo_manager.egg-info/SOURCES.txt +6 -0
- cyclo_manager-0.2.0/cyclo_manager.egg-info/entry_points.txt +4 -0
- cyclo_manager-0.2.0/cyclo_manager.egg-info/requires.txt +7 -0
- {cyclo_manager-0.1.1.dev1 → cyclo_manager-0.2.0}/cyclo_manager.egg-info/top_level.txt +1 -0
- {cyclo_manager-0.1.1.dev1 → cyclo_manager-0.2.0}/cyclo_manager_cli/__init__.py +1 -1
- cyclo_manager-0.2.0/cyclo_manager_cli/cli.py +423 -0
- cyclo_manager-0.2.0/cyclo_manager_cli/config/config.yml +8 -0
- {cyclo_manager-0.1.1.dev1 → cyclo_manager-0.2.0}/cyclo_manager_cli/docker/docker-compose.yml +4 -2
- {cyclo_manager-0.1.1.dev1 → cyclo_manager-0.2.0}/pyproject.toml +9 -3
- {cyclo_manager-0.1.1.dev1 → cyclo_manager-0.2.0}/setup.cfg +1 -1
- cyclo_manager-0.1.1.dev1/PKG-INFO +0 -138
- cyclo_manager-0.1.1.dev1/README.md +0 -127
- cyclo_manager-0.1.1.dev1/cyclo_manager.egg-info/PKG-INFO +0 -138
- cyclo_manager-0.1.1.dev1/cyclo_manager.egg-info/entry_points.txt +0 -2
- cyclo_manager-0.1.1.dev1/cyclo_manager.egg-info/requires.txt +0 -4
- cyclo_manager-0.1.1.dev1/cyclo_manager_cli/cli.py +0 -189
- cyclo_manager-0.1.1.dev1/cyclo_manager_cli/config/config.yml +0 -10
- {cyclo_manager-0.1.1.dev1 → cyclo_manager-0.2.0}/cyclo_manager.egg-info/dependency_links.txt +0 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cyclo-manager
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: cyclo_manager CLI: pip-installable launcher for cyclo_manager server and UI containers. Run 'cyclo_manager up' to start Docker stack.
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: fastapi>=0.100.0
|
|
9
|
+
Requires-Dist: uvicorn[standard]>=0.23.0
|
|
10
|
+
Requires-Dist: psutil>=5.9.0
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest; extra == "dev"
|
|
13
|
+
Requires-Dist: ruff; extra == "dev"
|
|
14
|
+
|
|
15
|
+
# cyclo_manager CLI
|
|
16
|
+
|
|
17
|
+
**PyPI package:** `cyclo-manager`
|
|
18
|
+
**Console commands:** `cyclo_manager` (underscore), `cyclo-manager` (hyphen)
|
|
19
|
+
**Host agent:** `cyclo_host_agent` (installed by the package, run via systemd)
|
|
20
|
+
|
|
21
|
+
This package is the **pip-installable launcher** for the cyclo_manager stack on a robot host. It:
|
|
22
|
+
|
|
23
|
+
1. Runs **`docker compose`** against a **bundled** [`cyclo_manager_cli/docker/docker-compose.yml`](cyclo_manager_cli/docker/docker-compose.yml) to start the **API** and **web UI** containers.
|
|
24
|
+
2. Installs and maintains **`cyclo_host_agent`** as a **systemd** service for host-level operations (git repo updates, `cyclo_manager` package update from the UI).
|
|
25
|
+
3. Can **`pip install -U`** itself and bring the Docker stack back up.
|
|
26
|
+
|
|
27
|
+
Implementation: [`cyclo_manager_cli/cli.py`](cyclo_manager_cli/cli.py), [`cyclo_host_agent/`](cyclo_host_agent/).
|
|
28
|
+
|
|
29
|
+
For the full monorepo (API source, UI source, dev compose), see the [repository root README](../README.md).
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Table of contents
|
|
34
|
+
|
|
35
|
+
- [Prerequisites](#prerequisites)
|
|
36
|
+
- [Install](#install)
|
|
37
|
+
- [Commands](#commands)
|
|
38
|
+
- [What `cyclo_manager up` does](#what-cyclo_manager-up-does)
|
|
39
|
+
- [Host agent (`cyclo_host_agent`)](#host-agent-cyclo_host_agent)
|
|
40
|
+
- [Compose services](#compose-services)
|
|
41
|
+
- [Configuration](#configuration)
|
|
42
|
+
- [Agent sockets on the host](#agent-sockets-on-the-host)
|
|
43
|
+
- [Environment variables](#environment-variables)
|
|
44
|
+
- [URLs (packaged stack)](#urls-packaged-stack)
|
|
45
|
+
- [Custom config and development](#custom-config-and-development)
|
|
46
|
+
- [Dependencies](#dependencies)
|
|
47
|
+
- [License](#license)
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Prerequisites
|
|
52
|
+
|
|
53
|
+
- **Docker** with **Compose v2** (`docker compose` — the legacy `docker-compose` binary alone is not enough)
|
|
54
|
+
- **Python 3.10+**
|
|
55
|
+
- **`sudo`** — required to install the host agent systemd unit, socket directory permissions, and sudoers rules
|
|
56
|
+
- For **`cyclo_manager update`:** **`pip`** or **`pip3`** on `PATH`
|
|
57
|
+
- **Do not run as root** — the host agent runs as the invoking user (or `SUDO_USER` when using `sudo cyclo_manager up`). Running as root is rejected.
|
|
58
|
+
- **Agent sockets** on the host under `/var/run/robotis/agent_sockets/` (robot containers and host agent). The bundled Compose file bind-mounts this tree into the API container as `/agents/`.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Install
|
|
63
|
+
|
|
64
|
+
From **PyPI**:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install cyclo-manager
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
If `cyclo_manager` is not on `PATH`, add the user script directory:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
74
|
+
cyclo_manager up
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
If the entry point is missing:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
python3 -m cyclo_manager_cli.cli --help
|
|
81
|
+
python3 -m cyclo_manager_cli.cli up
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Commands
|
|
87
|
+
|
|
88
|
+
| Command | Behavior |
|
|
89
|
+
|--------|----------|
|
|
90
|
+
| `cyclo_manager up` | Install/refresh **cyclo_host_agent** (systemd unit, socket dir, sudoers), then `docker compose up -d` for **API + UI**, then `docker compose create --no-recreate` for **Zenoh** and **noVNC** (created but **not started**). Sets **`CYCLO_MANAGER_CONFIG_FILE`** to the bundled config. |
|
|
91
|
+
| `cyclo_manager up --pull` | `docker compose pull` first, then the same as `up`. |
|
|
92
|
+
| `cyclo_manager down` | **Tear down host agent** (stop/disable service, remove unit, sudoers, socket dir), then `docker compose down` for all services in the bundled compose file. |
|
|
93
|
+
| `cyclo_manager update` | `docker compose down` (containers only — **host agent stays installed**), `pip install -U cyclo-manager`, `docker compose up` + create optional services, then **refresh host agent** (unit, socket, sudoers). Optional `--pull` pulls images before the final `up`. |
|
|
94
|
+
| `cyclo_manager --help` | Subcommand overview |
|
|
95
|
+
|
|
96
|
+
**`down` vs `update`:** `down` removes the host agent entirely. `update` only stops Docker containers, upgrades the pip package, restarts the stack, and refreshes the host agent — it does **not** call `cyclo_manager down`.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## What `cyclo_manager up` does
|
|
101
|
+
|
|
102
|
+
Three phases:
|
|
103
|
+
|
|
104
|
+
```text
|
|
105
|
+
[1/3] Setting up host agent → systemd unit, socket ownership, sudoers
|
|
106
|
+
[2/3] Starting containers → cyclo_manager (API) + ui
|
|
107
|
+
[3/3] Creating (not starting) → zenoh_daemon + novnc-server
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Open **http://127.0.0.1:3000** (UI) and **http://127.0.0.1:8081/docs** (API).
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Host agent (`cyclo_host_agent`)
|
|
115
|
+
|
|
116
|
+
The pip package installs a small **FastAPI** server that listens on a **Unix domain socket** on the host:
|
|
117
|
+
|
|
118
|
+
| Item | Path |
|
|
119
|
+
|------|------|
|
|
120
|
+
| Socket | `/var/run/robotis/agent_sockets/host/host_agent.sock` |
|
|
121
|
+
| systemd unit | `cyclo_host_agent.service` |
|
|
122
|
+
| sudoers | `/etc/sudoers.d/cyclo_manager` (`cp`, `udevadm` for container setup scripts) |
|
|
123
|
+
|
|
124
|
+
The API container reaches it at `/agents/host/host_agent.sock` (see bundled `config.yml`).
|
|
125
|
+
|
|
126
|
+
**Responsibilities:**
|
|
127
|
+
|
|
128
|
+
- List and update **`~/ROBOTIS-GIT/*`** git repositories (Version Management in the UI)
|
|
129
|
+
- Run **`cyclo_manager update`** when triggered from the UI (`POST /host/update`)
|
|
130
|
+
|
|
131
|
+
`cyclo_manager up` and `cyclo_manager update` both call `_ensure_host_agent()`, which is idempotent — safe to run after upgrades or user changes.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Compose services
|
|
136
|
+
|
|
137
|
+
Defined in [`cyclo_manager_cli/docker/docker-compose.yml`](cyclo_manager_cli/docker/docker-compose.yml). All services use **`network_mode: host`**.
|
|
138
|
+
|
|
139
|
+
| Compose service | Container name | `cyclo_manager up` | Image (example) |
|
|
140
|
+
|-----------------|----------------|--------------------|-----------------|
|
|
141
|
+
| `cyclo_manager` | `cyclo_manager` | **Started** | `robotis/cyclo-manager:0.2.0` |
|
|
142
|
+
| `ui` | `cyclo_manager_ui` | **Started** | `robotis/cyclo-manager-ui:0.2.0` |
|
|
143
|
+
| `rmw_zenoh` | `zenoh_daemon` | **Created only** | `robotis/zenoh-daemon:latest` |
|
|
144
|
+
| `novnc-server` | `novnc-server` | **Created only** | `robotis/novnc-server:latest` |
|
|
145
|
+
|
|
146
|
+
Start optional containers from the UI or manually, e.g. `docker start zenoh_daemon`.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Configuration
|
|
151
|
+
|
|
152
|
+
The CLI always mounts the **bundled** config into the API container. There is **no** `-c` / `--config` flag on the pip CLI.
|
|
153
|
+
|
|
154
|
+
**Bundled path:** [`cyclo_manager_cli/config/config.yml`](cyclo_manager_cli/config/config.yml)
|
|
155
|
+
|
|
156
|
+
The API reads it as `CONFIG_FILE=/app/config.yml` inside the container.
|
|
157
|
+
|
|
158
|
+
### Schema
|
|
159
|
+
|
|
160
|
+
| Key | Description |
|
|
161
|
+
|-----|-------------|
|
|
162
|
+
| **`robot_container`** | Primary robot Docker container name (e.g. `ai_worker`). Used by the UI System page and service bringup. Must be a key in `sockets` and cannot be `host_agent`. |
|
|
163
|
+
| **`sockets`** | Map of logical name → agent **Unix socket path as seen inside the API container** (under `/agents/...`). Include robot/service containers and `host_agent`. |
|
|
164
|
+
|
|
165
|
+
s6 **service names** are not listed in config; each in-container agent reports them at runtime.
|
|
166
|
+
|
|
167
|
+
### Example (bundled default)
|
|
168
|
+
|
|
169
|
+
```yaml
|
|
170
|
+
robot_container: ai_worker
|
|
171
|
+
|
|
172
|
+
sockets:
|
|
173
|
+
ai_worker: "/agents/ai_worker/s6_agent.sock"
|
|
174
|
+
physical_ai_server: "/agents/physical_ai_server/s6_agent.sock"
|
|
175
|
+
host_agent: "/agents/host/host_agent.sock"
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Validation (API startup)
|
|
179
|
+
|
|
180
|
+
- At least one socket entry besides `host_agent`
|
|
181
|
+
- `robot_container` must exist in `sockets`
|
|
182
|
+
- All socket paths must be non-empty strings
|
|
183
|
+
|
|
184
|
+
To use a different config layout, mounts, or local source builds, use the repository’s **`docker-compose.dev.yml`** — see [Custom config and development](#custom-config-and-development).
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Agent sockets on the host
|
|
189
|
+
|
|
190
|
+
On the host, sockets typically live under:
|
|
191
|
+
|
|
192
|
+
```text
|
|
193
|
+
/var/run/robotis/agent_sockets/
|
|
194
|
+
├── ai_worker/s6_agent.sock
|
|
195
|
+
├── physical_ai_server/s6_agent.sock
|
|
196
|
+
└── host/host_agent.sock ← created by cyclo_manager up
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The bundled compose file mounts the parent directory:
|
|
200
|
+
|
|
201
|
+
```yaml
|
|
202
|
+
- /var/run/robotis/agent_sockets:/agents
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
So `/agents/ai_worker/s6_agent.sock` in config corresponds to the host path above.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Environment variables
|
|
210
|
+
|
|
211
|
+
| Variable | Set by | Purpose |
|
|
212
|
+
|----------|--------|---------|
|
|
213
|
+
| **`CYCLO_MANAGER_CONFIG_FILE`** | CLI (`up`, `down`, `update`) | Absolute path to bundled `config.yml` on the host; mounted into the API container |
|
|
214
|
+
| **`HOSTNAME`** | CLI (default: machine hostname) | Passed to API as `HOST_HOSTNAME` |
|
|
215
|
+
| **`CONFIG_FILE`** | Compose (`/app/config.yml`) | Path inside the API container |
|
|
216
|
+
| **`ROS_DOMAIN_ID`** | **Not** set by CLI | Set inside robot containers (e.g. `~/.bashrc`) so DDS matches your fleet |
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## URLs (packaged stack)
|
|
221
|
+
|
|
222
|
+
With `network_mode: host` and default ports:
|
|
223
|
+
|
|
224
|
+
| What | URL |
|
|
225
|
+
|------|-----|
|
|
226
|
+
| Web UI | http://127.0.0.1:3000 |
|
|
227
|
+
| cyclo_manager API | http://127.0.0.1:8081 |
|
|
228
|
+
| OpenAPI (Swagger) | http://127.0.0.1:8081/docs |
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Custom config and development
|
|
233
|
+
|
|
234
|
+
The pip workflow is intended for **production robots** with pre-built images.
|
|
235
|
+
|
|
236
|
+
For development (local API/UI source, custom `config.yml`, hot reload):
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
# From repository root
|
|
240
|
+
docker compose -f docker-compose.dev.yml up
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
See the [root README](../README.md#development).
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Dependencies
|
|
248
|
+
|
|
249
|
+
Python dependencies declared in [`pyproject.toml`](pyproject.toml):
|
|
250
|
+
|
|
251
|
+
| Package | Used by |
|
|
252
|
+
|---------|---------|
|
|
253
|
+
| **fastapi** | `cyclo_host_agent` HTTP API |
|
|
254
|
+
| **uvicorn** | `cyclo_host_agent` server |
|
|
255
|
+
| **psutil** | Host agent / system utilities |
|
|
256
|
+
|
|
257
|
+
The **`cyclo_manager`** CLI itself uses only the Python standard library plus **`subprocess`** calls to **`docker compose`**, **`pip`**, **`systemctl`**, and **`sudo`**.
|
|
258
|
+
|
|
259
|
+
Runtime **outside** Python:
|
|
260
|
+
|
|
261
|
+
- **Docker** + **Compose v2**
|
|
262
|
+
- **systemd** (host agent service)
|
|
263
|
+
- Pre-built **Docker images** referenced in the bundled compose file
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## License
|
|
268
|
+
|
|
269
|
+
SPDX: **Apache-2.0** (see [pyproject.toml](pyproject.toml) and the repository [LICENSE](../LICENSE)).
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# cyclo_manager CLI
|
|
2
|
+
|
|
3
|
+
**PyPI package:** `cyclo-manager`
|
|
4
|
+
**Console commands:** `cyclo_manager` (underscore), `cyclo-manager` (hyphen)
|
|
5
|
+
**Host agent:** `cyclo_host_agent` (installed by the package, run via systemd)
|
|
6
|
+
|
|
7
|
+
This package is the **pip-installable launcher** for the cyclo_manager stack on a robot host. It:
|
|
8
|
+
|
|
9
|
+
1. Runs **`docker compose`** against a **bundled** [`cyclo_manager_cli/docker/docker-compose.yml`](cyclo_manager_cli/docker/docker-compose.yml) to start the **API** and **web UI** containers.
|
|
10
|
+
2. Installs and maintains **`cyclo_host_agent`** as a **systemd** service for host-level operations (git repo updates, `cyclo_manager` package update from the UI).
|
|
11
|
+
3. Can **`pip install -U`** itself and bring the Docker stack back up.
|
|
12
|
+
|
|
13
|
+
Implementation: [`cyclo_manager_cli/cli.py`](cyclo_manager_cli/cli.py), [`cyclo_host_agent/`](cyclo_host_agent/).
|
|
14
|
+
|
|
15
|
+
For the full monorepo (API source, UI source, dev compose), see the [repository root README](../README.md).
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Table of contents
|
|
20
|
+
|
|
21
|
+
- [Prerequisites](#prerequisites)
|
|
22
|
+
- [Install](#install)
|
|
23
|
+
- [Commands](#commands)
|
|
24
|
+
- [What `cyclo_manager up` does](#what-cyclo_manager-up-does)
|
|
25
|
+
- [Host agent (`cyclo_host_agent`)](#host-agent-cyclo_host_agent)
|
|
26
|
+
- [Compose services](#compose-services)
|
|
27
|
+
- [Configuration](#configuration)
|
|
28
|
+
- [Agent sockets on the host](#agent-sockets-on-the-host)
|
|
29
|
+
- [Environment variables](#environment-variables)
|
|
30
|
+
- [URLs (packaged stack)](#urls-packaged-stack)
|
|
31
|
+
- [Custom config and development](#custom-config-and-development)
|
|
32
|
+
- [Dependencies](#dependencies)
|
|
33
|
+
- [License](#license)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Prerequisites
|
|
38
|
+
|
|
39
|
+
- **Docker** with **Compose v2** (`docker compose` — the legacy `docker-compose` binary alone is not enough)
|
|
40
|
+
- **Python 3.10+**
|
|
41
|
+
- **`sudo`** — required to install the host agent systemd unit, socket directory permissions, and sudoers rules
|
|
42
|
+
- For **`cyclo_manager update`:** **`pip`** or **`pip3`** on `PATH`
|
|
43
|
+
- **Do not run as root** — the host agent runs as the invoking user (or `SUDO_USER` when using `sudo cyclo_manager up`). Running as root is rejected.
|
|
44
|
+
- **Agent sockets** on the host under `/var/run/robotis/agent_sockets/` (robot containers and host agent). The bundled Compose file bind-mounts this tree into the API container as `/agents/`.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
From **PyPI**:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install cyclo-manager
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
If `cyclo_manager` is not on `PATH`, add the user script directory:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
60
|
+
cyclo_manager up
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
If the entry point is missing:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
python3 -m cyclo_manager_cli.cli --help
|
|
67
|
+
python3 -m cyclo_manager_cli.cli up
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Commands
|
|
73
|
+
|
|
74
|
+
| Command | Behavior |
|
|
75
|
+
|--------|----------|
|
|
76
|
+
| `cyclo_manager up` | Install/refresh **cyclo_host_agent** (systemd unit, socket dir, sudoers), then `docker compose up -d` for **API + UI**, then `docker compose create --no-recreate` for **Zenoh** and **noVNC** (created but **not started**). Sets **`CYCLO_MANAGER_CONFIG_FILE`** to the bundled config. |
|
|
77
|
+
| `cyclo_manager up --pull` | `docker compose pull` first, then the same as `up`. |
|
|
78
|
+
| `cyclo_manager down` | **Tear down host agent** (stop/disable service, remove unit, sudoers, socket dir), then `docker compose down` for all services in the bundled compose file. |
|
|
79
|
+
| `cyclo_manager update` | `docker compose down` (containers only — **host agent stays installed**), `pip install -U cyclo-manager`, `docker compose up` + create optional services, then **refresh host agent** (unit, socket, sudoers). Optional `--pull` pulls images before the final `up`. |
|
|
80
|
+
| `cyclo_manager --help` | Subcommand overview |
|
|
81
|
+
|
|
82
|
+
**`down` vs `update`:** `down` removes the host agent entirely. `update` only stops Docker containers, upgrades the pip package, restarts the stack, and refreshes the host agent — it does **not** call `cyclo_manager down`.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## What `cyclo_manager up` does
|
|
87
|
+
|
|
88
|
+
Three phases:
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
[1/3] Setting up host agent → systemd unit, socket ownership, sudoers
|
|
92
|
+
[2/3] Starting containers → cyclo_manager (API) + ui
|
|
93
|
+
[3/3] Creating (not starting) → zenoh_daemon + novnc-server
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Open **http://127.0.0.1:3000** (UI) and **http://127.0.0.1:8081/docs** (API).
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Host agent (`cyclo_host_agent`)
|
|
101
|
+
|
|
102
|
+
The pip package installs a small **FastAPI** server that listens on a **Unix domain socket** on the host:
|
|
103
|
+
|
|
104
|
+
| Item | Path |
|
|
105
|
+
|------|------|
|
|
106
|
+
| Socket | `/var/run/robotis/agent_sockets/host/host_agent.sock` |
|
|
107
|
+
| systemd unit | `cyclo_host_agent.service` |
|
|
108
|
+
| sudoers | `/etc/sudoers.d/cyclo_manager` (`cp`, `udevadm` for container setup scripts) |
|
|
109
|
+
|
|
110
|
+
The API container reaches it at `/agents/host/host_agent.sock` (see bundled `config.yml`).
|
|
111
|
+
|
|
112
|
+
**Responsibilities:**
|
|
113
|
+
|
|
114
|
+
- List and update **`~/ROBOTIS-GIT/*`** git repositories (Version Management in the UI)
|
|
115
|
+
- Run **`cyclo_manager update`** when triggered from the UI (`POST /host/update`)
|
|
116
|
+
|
|
117
|
+
`cyclo_manager up` and `cyclo_manager update` both call `_ensure_host_agent()`, which is idempotent — safe to run after upgrades or user changes.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Compose services
|
|
122
|
+
|
|
123
|
+
Defined in [`cyclo_manager_cli/docker/docker-compose.yml`](cyclo_manager_cli/docker/docker-compose.yml). All services use **`network_mode: host`**.
|
|
124
|
+
|
|
125
|
+
| Compose service | Container name | `cyclo_manager up` | Image (example) |
|
|
126
|
+
|-----------------|----------------|--------------------|-----------------|
|
|
127
|
+
| `cyclo_manager` | `cyclo_manager` | **Started** | `robotis/cyclo-manager:0.2.0` |
|
|
128
|
+
| `ui` | `cyclo_manager_ui` | **Started** | `robotis/cyclo-manager-ui:0.2.0` |
|
|
129
|
+
| `rmw_zenoh` | `zenoh_daemon` | **Created only** | `robotis/zenoh-daemon:latest` |
|
|
130
|
+
| `novnc-server` | `novnc-server` | **Created only** | `robotis/novnc-server:latest` |
|
|
131
|
+
|
|
132
|
+
Start optional containers from the UI or manually, e.g. `docker start zenoh_daemon`.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Configuration
|
|
137
|
+
|
|
138
|
+
The CLI always mounts the **bundled** config into the API container. There is **no** `-c` / `--config` flag on the pip CLI.
|
|
139
|
+
|
|
140
|
+
**Bundled path:** [`cyclo_manager_cli/config/config.yml`](cyclo_manager_cli/config/config.yml)
|
|
141
|
+
|
|
142
|
+
The API reads it as `CONFIG_FILE=/app/config.yml` inside the container.
|
|
143
|
+
|
|
144
|
+
### Schema
|
|
145
|
+
|
|
146
|
+
| Key | Description |
|
|
147
|
+
|-----|-------------|
|
|
148
|
+
| **`robot_container`** | Primary robot Docker container name (e.g. `ai_worker`). Used by the UI System page and service bringup. Must be a key in `sockets` and cannot be `host_agent`. |
|
|
149
|
+
| **`sockets`** | Map of logical name → agent **Unix socket path as seen inside the API container** (under `/agents/...`). Include robot/service containers and `host_agent`. |
|
|
150
|
+
|
|
151
|
+
s6 **service names** are not listed in config; each in-container agent reports them at runtime.
|
|
152
|
+
|
|
153
|
+
### Example (bundled default)
|
|
154
|
+
|
|
155
|
+
```yaml
|
|
156
|
+
robot_container: ai_worker
|
|
157
|
+
|
|
158
|
+
sockets:
|
|
159
|
+
ai_worker: "/agents/ai_worker/s6_agent.sock"
|
|
160
|
+
physical_ai_server: "/agents/physical_ai_server/s6_agent.sock"
|
|
161
|
+
host_agent: "/agents/host/host_agent.sock"
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Validation (API startup)
|
|
165
|
+
|
|
166
|
+
- At least one socket entry besides `host_agent`
|
|
167
|
+
- `robot_container` must exist in `sockets`
|
|
168
|
+
- All socket paths must be non-empty strings
|
|
169
|
+
|
|
170
|
+
To use a different config layout, mounts, or local source builds, use the repository’s **`docker-compose.dev.yml`** — see [Custom config and development](#custom-config-and-development).
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Agent sockets on the host
|
|
175
|
+
|
|
176
|
+
On the host, sockets typically live under:
|
|
177
|
+
|
|
178
|
+
```text
|
|
179
|
+
/var/run/robotis/agent_sockets/
|
|
180
|
+
├── ai_worker/s6_agent.sock
|
|
181
|
+
├── physical_ai_server/s6_agent.sock
|
|
182
|
+
└── host/host_agent.sock ← created by cyclo_manager up
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The bundled compose file mounts the parent directory:
|
|
186
|
+
|
|
187
|
+
```yaml
|
|
188
|
+
- /var/run/robotis/agent_sockets:/agents
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
So `/agents/ai_worker/s6_agent.sock` in config corresponds to the host path above.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Environment variables
|
|
196
|
+
|
|
197
|
+
| Variable | Set by | Purpose |
|
|
198
|
+
|----------|--------|---------|
|
|
199
|
+
| **`CYCLO_MANAGER_CONFIG_FILE`** | CLI (`up`, `down`, `update`) | Absolute path to bundled `config.yml` on the host; mounted into the API container |
|
|
200
|
+
| **`HOSTNAME`** | CLI (default: machine hostname) | Passed to API as `HOST_HOSTNAME` |
|
|
201
|
+
| **`CONFIG_FILE`** | Compose (`/app/config.yml`) | Path inside the API container |
|
|
202
|
+
| **`ROS_DOMAIN_ID`** | **Not** set by CLI | Set inside robot containers (e.g. `~/.bashrc`) so DDS matches your fleet |
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## URLs (packaged stack)
|
|
207
|
+
|
|
208
|
+
With `network_mode: host` and default ports:
|
|
209
|
+
|
|
210
|
+
| What | URL |
|
|
211
|
+
|------|-----|
|
|
212
|
+
| Web UI | http://127.0.0.1:3000 |
|
|
213
|
+
| cyclo_manager API | http://127.0.0.1:8081 |
|
|
214
|
+
| OpenAPI (Swagger) | http://127.0.0.1:8081/docs |
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Custom config and development
|
|
219
|
+
|
|
220
|
+
The pip workflow is intended for **production robots** with pre-built images.
|
|
221
|
+
|
|
222
|
+
For development (local API/UI source, custom `config.yml`, hot reload):
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
# From repository root
|
|
226
|
+
docker compose -f docker-compose.dev.yml up
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
See the [root README](../README.md#development).
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Dependencies
|
|
234
|
+
|
|
235
|
+
Python dependencies declared in [`pyproject.toml`](pyproject.toml):
|
|
236
|
+
|
|
237
|
+
| Package | Used by |
|
|
238
|
+
|---------|---------|
|
|
239
|
+
| **fastapi** | `cyclo_host_agent` HTTP API |
|
|
240
|
+
| **uvicorn** | `cyclo_host_agent` server |
|
|
241
|
+
| **psutil** | Host agent / system utilities |
|
|
242
|
+
|
|
243
|
+
The **`cyclo_manager`** CLI itself uses only the Python standard library plus **`subprocess`** calls to **`docker compose`**, **`pip`**, **`systemctl`**, and **`sudo`**.
|
|
244
|
+
|
|
245
|
+
Runtime **outside** Python:
|
|
246
|
+
|
|
247
|
+
- **Docker** + **Compose v2**
|
|
248
|
+
- **systemd** (host agent service)
|
|
249
|
+
- Pre-built **Docker images** referenced in the bundled compose file
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## License
|
|
254
|
+
|
|
255
|
+
SPDX: **Apache-2.0** (see [pyproject.toml](pyproject.toml) and the repository [LICENSE](../LICENSE)).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 ROBOTIS CO., LTD.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
#
|
|
17
|
+
# Author: Hyungyu Kim
|
|
18
|
+
|
|
19
|
+
"""cyclo_host_agent: host-side agent for system stats and repo management."""
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 ROBOTIS CO., LTD.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
#
|
|
17
|
+
# Author: Hyungyu Kim
|
|
18
|
+
|
|
19
|
+
"""Cyclo host agent: FastAPI server on Unix Domain Socket for host-level operations."""
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
from cyclo_host_agent.routers import repos, update
|
|
25
|
+
from fastapi import FastAPI
|
|
26
|
+
import uvicorn
|
|
27
|
+
|
|
28
|
+
logging.basicConfig(
|
|
29
|
+
level=logging.INFO,
|
|
30
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
31
|
+
)
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
SOCKET_PATH = '/var/run/robotis/agent_sockets/host/host_agent.sock'
|
|
35
|
+
|
|
36
|
+
app = FastAPI(
|
|
37
|
+
title='cyclo_host_agent',
|
|
38
|
+
description='Host agent for Cyclo Manager: repo management.',
|
|
39
|
+
version='0.2.0',
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
app.include_router(repos.router)
|
|
43
|
+
app.include_router(update.router)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def main() -> None:
|
|
47
|
+
"""Entry point for the host agent; called by systemd via ExecStart."""
|
|
48
|
+
socket_path = Path(SOCKET_PATH)
|
|
49
|
+
socket_path.parent.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
socket_path.unlink(missing_ok=True)
|
|
51
|
+
logger.info('Starting on %s', socket_path)
|
|
52
|
+
uvicorn.run(app, uds=str(socket_path), log_level='info')
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == '__main__':
|
|
56
|
+
main()
|