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.
Files changed (27) hide show
  1. cyclo_manager-0.2.0/PKG-INFO +269 -0
  2. cyclo_manager-0.2.0/README.md +255 -0
  3. cyclo_manager-0.2.0/cyclo_host_agent/__init__.py +19 -0
  4. cyclo_manager-0.2.0/cyclo_host_agent/main.py +56 -0
  5. cyclo_manager-0.2.0/cyclo_host_agent/models.py +105 -0
  6. cyclo_manager-0.2.0/cyclo_host_agent/routers/__init__.py +19 -0
  7. cyclo_manager-0.2.0/cyclo_host_agent/routers/repos.py +415 -0
  8. cyclo_manager-0.2.0/cyclo_host_agent/routers/update.py +77 -0
  9. cyclo_manager-0.2.0/cyclo_manager.egg-info/PKG-INFO +269 -0
  10. {cyclo_manager-0.1.1.dev1 → cyclo_manager-0.2.0}/cyclo_manager.egg-info/SOURCES.txt +6 -0
  11. cyclo_manager-0.2.0/cyclo_manager.egg-info/entry_points.txt +4 -0
  12. cyclo_manager-0.2.0/cyclo_manager.egg-info/requires.txt +7 -0
  13. {cyclo_manager-0.1.1.dev1 → cyclo_manager-0.2.0}/cyclo_manager.egg-info/top_level.txt +1 -0
  14. {cyclo_manager-0.1.1.dev1 → cyclo_manager-0.2.0}/cyclo_manager_cli/__init__.py +1 -1
  15. cyclo_manager-0.2.0/cyclo_manager_cli/cli.py +423 -0
  16. cyclo_manager-0.2.0/cyclo_manager_cli/config/config.yml +8 -0
  17. {cyclo_manager-0.1.1.dev1 → cyclo_manager-0.2.0}/cyclo_manager_cli/docker/docker-compose.yml +4 -2
  18. {cyclo_manager-0.1.1.dev1 → cyclo_manager-0.2.0}/pyproject.toml +9 -3
  19. {cyclo_manager-0.1.1.dev1 → cyclo_manager-0.2.0}/setup.cfg +1 -1
  20. cyclo_manager-0.1.1.dev1/PKG-INFO +0 -138
  21. cyclo_manager-0.1.1.dev1/README.md +0 -127
  22. cyclo_manager-0.1.1.dev1/cyclo_manager.egg-info/PKG-INFO +0 -138
  23. cyclo_manager-0.1.1.dev1/cyclo_manager.egg-info/entry_points.txt +0 -2
  24. cyclo_manager-0.1.1.dev1/cyclo_manager.egg-info/requires.txt +0 -4
  25. cyclo_manager-0.1.1.dev1/cyclo_manager_cli/cli.py +0 -189
  26. cyclo_manager-0.1.1.dev1/cyclo_manager_cli/config/config.yml +0 -10
  27. {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()