cyclo-manager 0.4.0.dev0__tar.gz → 1.0.0.dev1__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.4.0.dev0 → cyclo_manager-1.0.0.dev1}/PKG-INFO +11 -12
- {cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/README.md +10 -11
- {cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/cyclo_host_agent/routers/repos.py +5 -2
- {cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/cyclo_manager.egg-info/PKG-INFO +11 -12
- {cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/cyclo_manager_cli/cli.py +80 -19
- {cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/cyclo_manager_cli/config/config.yml +4 -1
- {cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/pyproject.toml +1 -1
- {cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/cyclo_host_agent/__init__.py +0 -0
- {cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/cyclo_host_agent/main.py +0 -0
- {cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/cyclo_host_agent/models.py +0 -0
- {cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/cyclo_host_agent/routers/__init__.py +0 -0
- {cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/cyclo_host_agent/routers/update.py +0 -0
- {cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/cyclo_manager.egg-info/SOURCES.txt +0 -0
- {cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/cyclo_manager.egg-info/dependency_links.txt +0 -0
- {cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/cyclo_manager.egg-info/entry_points.txt +0 -0
- {cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/cyclo_manager.egg-info/requires.txt +0 -0
- {cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/cyclo_manager.egg-info/top_level.txt +0 -0
- {cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/cyclo_manager_cli/__init__.py +0 -0
- {cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/cyclo_manager_cli/docker/docker-compose.yml +0 -0
- {cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cyclo-manager
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0.dev1
|
|
4
4
|
Summary: cyclo_manager CLI: pip-installable launcher for cyclo_manager server and UI containers. Run 'cyclo_manager up' to start Docker stack.
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -52,9 +52,9 @@ For the full monorepo (API source, UI source, dev compose), see the [repository
|
|
|
52
52
|
|
|
53
53
|
- **Docker** with **Compose v2** (`docker compose` — the legacy `docker-compose` binary alone is not enough)
|
|
54
54
|
- **Python 3.10+**
|
|
55
|
-
- **`sudo`** — required to install the host agent systemd unit, socket directory permissions, and sudoers rules
|
|
55
|
+
- **`sudo`** — required on normal-user hosts to install the host agent systemd unit, socket directory permissions, and sudoers rules
|
|
56
56
|
- For **`cyclo_manager update`:** **`pip`** or **`pip3`** on `PATH`
|
|
57
|
-
- **
|
|
57
|
+
- **Root shell handling** — root is rejected when a normal login user exists. On root-only devices, the host agent is installed as root.
|
|
58
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
59
|
|
|
60
60
|
---
|
|
@@ -120,7 +120,7 @@ The pip package installs a small **FastAPI** server that listens on a **Unix dom
|
|
|
120
120
|
|------|------|
|
|
121
121
|
| Socket | `/var/run/robotis/agent_sockets/host/host_agent.sock` |
|
|
122
122
|
| systemd unit | `cyclo_host_agent.service` |
|
|
123
|
-
| sudoers | `/etc/sudoers.d/cyclo_manager` (`cp`, `udevadm` for container setup scripts) |
|
|
123
|
+
| sudoers | `/etc/sudoers.d/cyclo_manager` (`cp`, `udevadm` for container setup scripts; skipped on root-only devices) |
|
|
124
124
|
|
|
125
125
|
The API container reaches it at `/agents/host/host_agent.sock` (see bundled `config.yml`).
|
|
126
126
|
|
|
@@ -131,6 +131,8 @@ The API container reaches it at `/agents/host/host_agent.sock` (see bundled `con
|
|
|
131
131
|
|
|
132
132
|
`cyclo_manager up` and `cyclo_manager update` both call `_ensure_host_agent()`, which is idempotent — safe to run after upgrades or user changes.
|
|
133
133
|
|
|
134
|
+
Repository scanning uses `CYCLO_HOST_AGENT_WORKSPACE` when set. Otherwise it uses the service user's home directory, except root-only devices with `/data/docker`, where `/data/docker` is used automatically.
|
|
135
|
+
|
|
134
136
|
---
|
|
135
137
|
|
|
136
138
|
## Compose services
|
|
@@ -160,7 +162,7 @@ The API reads it as `CONFIG_FILE=/app/config.yml` inside the container.
|
|
|
160
162
|
|
|
161
163
|
| Key | Description |
|
|
162
164
|
|-----|-------------|
|
|
163
|
-
| **`
|
|
165
|
+
| **`supported_robot_containers`** | Robot Docker container names that can open the System page (e.g. `ai_worker`, `open_manipulator`). Each must be a key in `sockets` and cannot be `host_agent`. |
|
|
164
166
|
| **`sockets`** | Map of logical name → agent **Unix socket path as seen inside the API container** (under `/agents/...`). Include robot/service containers and `host_agent`. |
|
|
165
167
|
|
|
166
168
|
s6 **service names** are not listed in config; each in-container agent reports them at runtime.
|
|
@@ -168,20 +170,17 @@ s6 **service names** are not listed in config; each in-container agent reports t
|
|
|
168
170
|
### Example (bundled default)
|
|
169
171
|
|
|
170
172
|
```yaml
|
|
171
|
-
|
|
173
|
+
supported_robot_containers:
|
|
174
|
+
- ai_worker
|
|
175
|
+
- open_manipulator
|
|
172
176
|
|
|
173
177
|
sockets:
|
|
174
178
|
ai_worker: "/agents/ai_worker/s6_agent.sock"
|
|
179
|
+
open_manipulator: "/agents/open_manipulator/s6_agent.sock"
|
|
175
180
|
cyclo_intelligence: "/agents/cyclo_intelligence/s6_agent.sock"
|
|
176
181
|
host_agent: "/agents/host/host_agent.sock"
|
|
177
182
|
```
|
|
178
183
|
|
|
179
|
-
### Validation (API startup)
|
|
180
|
-
|
|
181
|
-
- At least one socket entry besides `host_agent`
|
|
182
|
-
- `robot_container` must exist in `sockets`
|
|
183
|
-
- All socket paths must be non-empty strings
|
|
184
|
-
|
|
185
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).
|
|
186
185
|
|
|
187
186
|
---
|
|
@@ -38,9 +38,9 @@ For the full monorepo (API source, UI source, dev compose), see the [repository
|
|
|
38
38
|
|
|
39
39
|
- **Docker** with **Compose v2** (`docker compose` — the legacy `docker-compose` binary alone is not enough)
|
|
40
40
|
- **Python 3.10+**
|
|
41
|
-
- **`sudo`** — required to install the host agent systemd unit, socket directory permissions, and sudoers rules
|
|
41
|
+
- **`sudo`** — required on normal-user hosts to install the host agent systemd unit, socket directory permissions, and sudoers rules
|
|
42
42
|
- For **`cyclo_manager update`:** **`pip`** or **`pip3`** on `PATH`
|
|
43
|
-
- **
|
|
43
|
+
- **Root shell handling** — root is rejected when a normal login user exists. On root-only devices, the host agent is installed as root.
|
|
44
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
45
|
|
|
46
46
|
---
|
|
@@ -106,7 +106,7 @@ The pip package installs a small **FastAPI** server that listens on a **Unix dom
|
|
|
106
106
|
|------|------|
|
|
107
107
|
| Socket | `/var/run/robotis/agent_sockets/host/host_agent.sock` |
|
|
108
108
|
| systemd unit | `cyclo_host_agent.service` |
|
|
109
|
-
| sudoers | `/etc/sudoers.d/cyclo_manager` (`cp`, `udevadm` for container setup scripts) |
|
|
109
|
+
| sudoers | `/etc/sudoers.d/cyclo_manager` (`cp`, `udevadm` for container setup scripts; skipped on root-only devices) |
|
|
110
110
|
|
|
111
111
|
The API container reaches it at `/agents/host/host_agent.sock` (see bundled `config.yml`).
|
|
112
112
|
|
|
@@ -117,6 +117,8 @@ The API container reaches it at `/agents/host/host_agent.sock` (see bundled `con
|
|
|
117
117
|
|
|
118
118
|
`cyclo_manager up` and `cyclo_manager update` both call `_ensure_host_agent()`, which is idempotent — safe to run after upgrades or user changes.
|
|
119
119
|
|
|
120
|
+
Repository scanning uses `CYCLO_HOST_AGENT_WORKSPACE` when set. Otherwise it uses the service user's home directory, except root-only devices with `/data/docker`, where `/data/docker` is used automatically.
|
|
121
|
+
|
|
120
122
|
---
|
|
121
123
|
|
|
122
124
|
## Compose services
|
|
@@ -146,7 +148,7 @@ The API reads it as `CONFIG_FILE=/app/config.yml` inside the container.
|
|
|
146
148
|
|
|
147
149
|
| Key | Description |
|
|
148
150
|
|-----|-------------|
|
|
149
|
-
| **`
|
|
151
|
+
| **`supported_robot_containers`** | Robot Docker container names that can open the System page (e.g. `ai_worker`, `open_manipulator`). Each must be a key in `sockets` and cannot be `host_agent`. |
|
|
150
152
|
| **`sockets`** | Map of logical name → agent **Unix socket path as seen inside the API container** (under `/agents/...`). Include robot/service containers and `host_agent`. |
|
|
151
153
|
|
|
152
154
|
s6 **service names** are not listed in config; each in-container agent reports them at runtime.
|
|
@@ -154,20 +156,17 @@ s6 **service names** are not listed in config; each in-container agent reports t
|
|
|
154
156
|
### Example (bundled default)
|
|
155
157
|
|
|
156
158
|
```yaml
|
|
157
|
-
|
|
159
|
+
supported_robot_containers:
|
|
160
|
+
- ai_worker
|
|
161
|
+
- open_manipulator
|
|
158
162
|
|
|
159
163
|
sockets:
|
|
160
164
|
ai_worker: "/agents/ai_worker/s6_agent.sock"
|
|
165
|
+
open_manipulator: "/agents/open_manipulator/s6_agent.sock"
|
|
161
166
|
cyclo_intelligence: "/agents/cyclo_intelligence/s6_agent.sock"
|
|
162
167
|
host_agent: "/agents/host/host_agent.sock"
|
|
163
168
|
```
|
|
164
169
|
|
|
165
|
-
### Validation (API startup)
|
|
166
|
-
|
|
167
|
-
- At least one socket entry besides `host_agent`
|
|
168
|
-
- `robot_container` must exist in `sockets`
|
|
169
|
-
- All socket paths must be non-empty strings
|
|
170
|
-
|
|
171
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).
|
|
172
171
|
|
|
173
172
|
---
|
|
@@ -43,14 +43,17 @@ router = APIRouter(prefix='/repos', tags=['repos'])
|
|
|
43
43
|
GIT_TIMEOUT = 120.0
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
def
|
|
46
|
+
def _resolve_workspace() -> Path:
|
|
47
|
+
configured_workspace = os.environ.get('CYCLO_HOST_AGENT_WORKSPACE')
|
|
48
|
+
if configured_workspace:
|
|
49
|
+
return Path(configured_workspace).expanduser()
|
|
47
50
|
sudo_user = os.environ.get('SUDO_USER')
|
|
48
51
|
if sudo_user:
|
|
49
52
|
return Path(f'/home/{sudo_user}')
|
|
50
53
|
return Path.home()
|
|
51
54
|
|
|
52
55
|
|
|
53
|
-
WORKSPACE_PATH =
|
|
56
|
+
WORKSPACE_PATH = _resolve_workspace()
|
|
54
57
|
MANAGED_GITHUB_ORG = 'ROBOTIS-GIT'
|
|
55
58
|
ALLOWED_UPDATE_BRANCHES = frozenset({'main', 'jazzy'})
|
|
56
59
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cyclo-manager
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0.dev1
|
|
4
4
|
Summary: cyclo_manager CLI: pip-installable launcher for cyclo_manager server and UI containers. Run 'cyclo_manager up' to start Docker stack.
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -52,9 +52,9 @@ For the full monorepo (API source, UI source, dev compose), see the [repository
|
|
|
52
52
|
|
|
53
53
|
- **Docker** with **Compose v2** (`docker compose` — the legacy `docker-compose` binary alone is not enough)
|
|
54
54
|
- **Python 3.10+**
|
|
55
|
-
- **`sudo`** — required to install the host agent systemd unit, socket directory permissions, and sudoers rules
|
|
55
|
+
- **`sudo`** — required on normal-user hosts to install the host agent systemd unit, socket directory permissions, and sudoers rules
|
|
56
56
|
- For **`cyclo_manager update`:** **`pip`** or **`pip3`** on `PATH`
|
|
57
|
-
- **
|
|
57
|
+
- **Root shell handling** — root is rejected when a normal login user exists. On root-only devices, the host agent is installed as root.
|
|
58
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
59
|
|
|
60
60
|
---
|
|
@@ -120,7 +120,7 @@ The pip package installs a small **FastAPI** server that listens on a **Unix dom
|
|
|
120
120
|
|------|------|
|
|
121
121
|
| Socket | `/var/run/robotis/agent_sockets/host/host_agent.sock` |
|
|
122
122
|
| systemd unit | `cyclo_host_agent.service` |
|
|
123
|
-
| sudoers | `/etc/sudoers.d/cyclo_manager` (`cp`, `udevadm` for container setup scripts) |
|
|
123
|
+
| sudoers | `/etc/sudoers.d/cyclo_manager` (`cp`, `udevadm` for container setup scripts; skipped on root-only devices) |
|
|
124
124
|
|
|
125
125
|
The API container reaches it at `/agents/host/host_agent.sock` (see bundled `config.yml`).
|
|
126
126
|
|
|
@@ -131,6 +131,8 @@ The API container reaches it at `/agents/host/host_agent.sock` (see bundled `con
|
|
|
131
131
|
|
|
132
132
|
`cyclo_manager up` and `cyclo_manager update` both call `_ensure_host_agent()`, which is idempotent — safe to run after upgrades or user changes.
|
|
133
133
|
|
|
134
|
+
Repository scanning uses `CYCLO_HOST_AGENT_WORKSPACE` when set. Otherwise it uses the service user's home directory, except root-only devices with `/data/docker`, where `/data/docker` is used automatically.
|
|
135
|
+
|
|
134
136
|
---
|
|
135
137
|
|
|
136
138
|
## Compose services
|
|
@@ -160,7 +162,7 @@ The API reads it as `CONFIG_FILE=/app/config.yml` inside the container.
|
|
|
160
162
|
|
|
161
163
|
| Key | Description |
|
|
162
164
|
|-----|-------------|
|
|
163
|
-
| **`
|
|
165
|
+
| **`supported_robot_containers`** | Robot Docker container names that can open the System page (e.g. `ai_worker`, `open_manipulator`). Each must be a key in `sockets` and cannot be `host_agent`. |
|
|
164
166
|
| **`sockets`** | Map of logical name → agent **Unix socket path as seen inside the API container** (under `/agents/...`). Include robot/service containers and `host_agent`. |
|
|
165
167
|
|
|
166
168
|
s6 **service names** are not listed in config; each in-container agent reports them at runtime.
|
|
@@ -168,20 +170,17 @@ s6 **service names** are not listed in config; each in-container agent reports t
|
|
|
168
170
|
### Example (bundled default)
|
|
169
171
|
|
|
170
172
|
```yaml
|
|
171
|
-
|
|
173
|
+
supported_robot_containers:
|
|
174
|
+
- ai_worker
|
|
175
|
+
- open_manipulator
|
|
172
176
|
|
|
173
177
|
sockets:
|
|
174
178
|
ai_worker: "/agents/ai_worker/s6_agent.sock"
|
|
179
|
+
open_manipulator: "/agents/open_manipulator/s6_agent.sock"
|
|
175
180
|
cyclo_intelligence: "/agents/cyclo_intelligence/s6_agent.sock"
|
|
176
181
|
host_agent: "/agents/host/host_agent.sock"
|
|
177
182
|
```
|
|
178
183
|
|
|
179
|
-
### Validation (API startup)
|
|
180
|
-
|
|
181
|
-
- At least one socket entry besides `host_agent`
|
|
182
|
-
- `robot_container` must exist in `sockets`
|
|
183
|
-
- All socket paths must be non-empty strings
|
|
184
|
-
|
|
185
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).
|
|
186
185
|
|
|
187
186
|
---
|
|
@@ -26,6 +26,7 @@ Launches cyclo_manager server and UI via Docker.
|
|
|
26
26
|
import argparse
|
|
27
27
|
import os
|
|
28
28
|
from pathlib import Path
|
|
29
|
+
import pwd
|
|
29
30
|
import shutil
|
|
30
31
|
import socket
|
|
31
32
|
import subprocess
|
|
@@ -40,6 +41,7 @@ HOST_AGENT_SOCKET_DIR = '/var/run/robotis/agent_sockets/host'
|
|
|
40
41
|
COMPOSE_SERVICES_UP = ('cyclo_manager', 'ui')
|
|
41
42
|
# These get `docker compose create` only (stopped); start from UI or `docker start`.
|
|
42
43
|
COMPOSE_SERVICES_CREATE_ONLY = ('rmw_zenoh', 'novnc-server')
|
|
44
|
+
DISABLED_LOGIN_SHELLS = {'', '/bin/false', '/sbin/nologin', '/usr/sbin/nologin'}
|
|
43
45
|
|
|
44
46
|
|
|
45
47
|
def _docker_dir() -> Path:
|
|
@@ -55,6 +57,47 @@ def _packaged_config_path() -> Path:
|
|
|
55
57
|
return _config_dir() / 'config.yml'
|
|
56
58
|
|
|
57
59
|
|
|
60
|
+
def _privileged_cmd(args: list[str]) -> list[str]:
|
|
61
|
+
"""Return args prefixed with sudo when the current process is not already root."""
|
|
62
|
+
if os.geteuid() == 0:
|
|
63
|
+
return args
|
|
64
|
+
return ['sudo', *args]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _user_home(user: str) -> Path:
|
|
68
|
+
"""Return a user's home directory from passwd, with a conservative fallback."""
|
|
69
|
+
try:
|
|
70
|
+
return Path(pwd.getpwnam(user).pw_dir)
|
|
71
|
+
except KeyError:
|
|
72
|
+
if user == 'root':
|
|
73
|
+
return Path('/root')
|
|
74
|
+
return Path(f'/home/{user}')
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _has_normal_login_user() -> bool:
|
|
78
|
+
"""Return True when the host has a non-root login user."""
|
|
79
|
+
for account in pwd.getpwall():
|
|
80
|
+
if account.pw_name == 'root':
|
|
81
|
+
continue
|
|
82
|
+
if account.pw_uid < 1000 or account.pw_uid >= 65534:
|
|
83
|
+
continue
|
|
84
|
+
if account.pw_shell in DISABLED_LOGIN_SHELLS:
|
|
85
|
+
continue
|
|
86
|
+
return True
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _host_agent_workspace(user: str, user_home: Path) -> Path:
|
|
91
|
+
"""Return the workspace path the host agent should scan for git repositories."""
|
|
92
|
+
configured_workspace = os.environ.get('CYCLO_HOST_AGENT_WORKSPACE')
|
|
93
|
+
if configured_workspace:
|
|
94
|
+
return Path(configured_workspace).expanduser()
|
|
95
|
+
data_docker = Path('/data/docker')
|
|
96
|
+
if user == 'root' and data_docker.is_dir():
|
|
97
|
+
return data_docker
|
|
98
|
+
return user_home
|
|
99
|
+
|
|
100
|
+
|
|
58
101
|
def _resolve_service_user() -> tuple[str, Path] | None:
|
|
59
102
|
"""
|
|
60
103
|
Return (username, home) for the host agent systemd service.
|
|
@@ -64,9 +107,15 @@ def _resolve_service_user() -> tuple[str, Path] | None:
|
|
|
64
107
|
"""
|
|
65
108
|
sudo_user = os.environ.get('SUDO_USER')
|
|
66
109
|
if sudo_user:
|
|
67
|
-
return sudo_user,
|
|
68
|
-
user = os.environ.get('USER') or os.
|
|
110
|
+
return sudo_user, _user_home(sudo_user)
|
|
111
|
+
user = os.environ.get('USER') or pwd.getpwuid(os.getuid()).pw_name
|
|
69
112
|
if user == 'root':
|
|
113
|
+
if not _has_normal_login_user():
|
|
114
|
+
print(
|
|
115
|
+
'Info: root-only device detected; cyclo_host_agent will run as root.',
|
|
116
|
+
file=sys.stderr,
|
|
117
|
+
)
|
|
118
|
+
return 'root', Path('/root')
|
|
70
119
|
print(
|
|
71
120
|
'Error: cyclo_manager up must run as a normal user (e.g. robotis), not root.',
|
|
72
121
|
file=sys.stderr,
|
|
@@ -85,19 +134,19 @@ def _setup_socket_dir(user: str) -> bool:
|
|
|
85
134
|
print(f' [1/3] Creating socket directory {HOST_AGENT_SOCKET_DIR}...')
|
|
86
135
|
try:
|
|
87
136
|
subprocess.run(
|
|
88
|
-
['
|
|
137
|
+
_privileged_cmd(['mkdir', '-p', HOST_AGENT_SOCKET_DIR]),
|
|
89
138
|
check=True,
|
|
90
139
|
capture_output=True,
|
|
91
140
|
)
|
|
92
141
|
print(f' [2/3] Setting ownership to {user}:{user}...')
|
|
93
142
|
subprocess.run(
|
|
94
|
-
['
|
|
143
|
+
_privileged_cmd(['chown', f'{user}:{user}', HOST_AGENT_SOCKET_DIR]),
|
|
95
144
|
check=True,
|
|
96
145
|
capture_output=True,
|
|
97
146
|
)
|
|
98
147
|
print(' [3/3] Removing stale socket file (if any)...')
|
|
99
148
|
subprocess.run(
|
|
100
|
-
['
|
|
149
|
+
_privileged_cmd(['rm', '-f', HOST_AGENT_SOCKET]),
|
|
101
150
|
check=True,
|
|
102
151
|
capture_output=True,
|
|
103
152
|
)
|
|
@@ -123,13 +172,13 @@ def _install_sudoers(user: str) -> bool:
|
|
|
123
172
|
sudoers_file = '/etc/sudoers.d/cyclo_manager'
|
|
124
173
|
try:
|
|
125
174
|
subprocess.run(
|
|
126
|
-
['
|
|
175
|
+
_privileged_cmd(['tee', sudoers_file]),
|
|
127
176
|
input=sudoers_content.encode(),
|
|
128
177
|
capture_output=True,
|
|
129
178
|
check=True,
|
|
130
179
|
)
|
|
131
180
|
subprocess.run(
|
|
132
|
-
['
|
|
181
|
+
_privileged_cmd(['chmod', '440', sudoers_file]),
|
|
133
182
|
check=True,
|
|
134
183
|
capture_output=True,
|
|
135
184
|
)
|
|
@@ -151,6 +200,7 @@ def _ensure_host_agent() -> int:
|
|
|
151
200
|
return 1
|
|
152
201
|
|
|
153
202
|
user, user_home = account
|
|
203
|
+
workspace_path = _host_agent_workspace(user, user_home)
|
|
154
204
|
agent_exe = shutil.which('cyclo_host_agent')
|
|
155
205
|
if not agent_exe:
|
|
156
206
|
print(
|
|
@@ -163,7 +213,7 @@ def _ensure_host_agent() -> int:
|
|
|
163
213
|
if not _setup_socket_dir(user):
|
|
164
214
|
return 1
|
|
165
215
|
|
|
166
|
-
if not _install_sudoers(user):
|
|
216
|
+
if user != 'root' and not _install_sudoers(user):
|
|
167
217
|
return 1
|
|
168
218
|
|
|
169
219
|
service_content = f"""\
|
|
@@ -179,6 +229,7 @@ RuntimeDirectory=robotis/agent_sockets/host
|
|
|
179
229
|
RuntimeDirectoryMode=0755
|
|
180
230
|
ExecStart={agent_exe}
|
|
181
231
|
Environment=HOME={user_home}
|
|
232
|
+
Environment=CYCLO_HOST_AGENT_WORKSPACE={workspace_path}
|
|
182
233
|
Restart=always
|
|
183
234
|
RestartSec=3
|
|
184
235
|
|
|
@@ -192,22 +243,26 @@ WantedBy=multi-user.target
|
|
|
192
243
|
try:
|
|
193
244
|
print(f' Writing unit file to {service_file}...')
|
|
194
245
|
subprocess.run(
|
|
195
|
-
['
|
|
246
|
+
_privileged_cmd(['tee', service_file]),
|
|
196
247
|
input=service_content.encode(),
|
|
197
248
|
capture_output=True,
|
|
198
249
|
check=True,
|
|
199
250
|
)
|
|
200
251
|
print(' Running systemctl daemon-reload...')
|
|
201
|
-
subprocess.run(
|
|
252
|
+
subprocess.run(
|
|
253
|
+
_privileged_cmd(['systemctl', 'daemon-reload']),
|
|
254
|
+
check=True,
|
|
255
|
+
capture_output=True,
|
|
256
|
+
)
|
|
202
257
|
print(f' Enabling {HOST_AGENT_SERVICE}...')
|
|
203
258
|
subprocess.run(
|
|
204
|
-
['
|
|
259
|
+
_privileged_cmd(['systemctl', 'enable', f'{HOST_AGENT_SERVICE}.service']),
|
|
205
260
|
check=True,
|
|
206
261
|
capture_output=True,
|
|
207
262
|
)
|
|
208
263
|
print(f' Starting {HOST_AGENT_SERVICE}...')
|
|
209
264
|
subprocess.run(
|
|
210
|
-
['
|
|
265
|
+
_privileged_cmd(['systemctl', 'restart', f'{HOST_AGENT_SERVICE}.service']),
|
|
211
266
|
check=True,
|
|
212
267
|
capture_output=True,
|
|
213
268
|
)
|
|
@@ -217,7 +272,7 @@ WantedBy=multi-user.target
|
|
|
217
272
|
print(f'Failed to install host agent service: {e}', file=sys.stderr)
|
|
218
273
|
return 1
|
|
219
274
|
except FileNotFoundError:
|
|
220
|
-
print('
|
|
275
|
+
print('Required command not found. Install the host agent service manually.', file=sys.stderr)
|
|
221
276
|
return 1
|
|
222
277
|
|
|
223
278
|
|
|
@@ -272,15 +327,21 @@ def _teardown_host_agent() -> None:
|
|
|
272
327
|
"""Stop and remove the host agent service, unit file, sudoers, and socket dir."""
|
|
273
328
|
print(f'Tearing down {HOST_AGENT_SERVICE}...')
|
|
274
329
|
steps = [
|
|
275
|
-
(['sudo', 'systemctl', 'stop', f'{HOST_AGENT_SERVICE}.service'], 'stop service'),
|
|
276
|
-
(['sudo', 'systemctl', 'disable', f'{HOST_AGENT_SERVICE}.service'], 'disable service'),
|
|
277
330
|
(
|
|
278
|
-
['
|
|
331
|
+
_privileged_cmd(['systemctl', 'stop', f'{HOST_AGENT_SERVICE}.service']),
|
|
332
|
+
'stop service',
|
|
333
|
+
),
|
|
334
|
+
(
|
|
335
|
+
_privileged_cmd(['systemctl', 'disable', f'{HOST_AGENT_SERVICE}.service']),
|
|
336
|
+
'disable service',
|
|
337
|
+
),
|
|
338
|
+
(
|
|
339
|
+
_privileged_cmd(['rm', '-f', f'/etc/systemd/system/{HOST_AGENT_SERVICE}.service']),
|
|
279
340
|
'remove unit file',
|
|
280
341
|
),
|
|
281
|
-
(['
|
|
282
|
-
(['
|
|
283
|
-
(['
|
|
342
|
+
(_privileged_cmd(['systemctl', 'daemon-reload']), 'daemon-reload'),
|
|
343
|
+
(_privileged_cmd(['rm', '-f', '/etc/sudoers.d/cyclo_manager']), 'remove sudoers'),
|
|
344
|
+
(_privileged_cmd(['rm', '-rf', HOST_AGENT_SOCKET_DIR]), 'remove socket dir'),
|
|
284
345
|
]
|
|
285
346
|
for cmd, desc in steps:
|
|
286
347
|
print(f' {desc}...')
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# cyclo_manager Configuration
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
supported_robot_containers:
|
|
4
|
+
- ai_worker
|
|
5
|
+
- open_manipulator
|
|
4
6
|
|
|
5
7
|
sockets:
|
|
6
8
|
ai_worker: "/agents/ai_worker/s6_agent.sock"
|
|
9
|
+
open_manipulator: "/agents/open_manipulator/s6_agent.sock"
|
|
7
10
|
cyclo_intelligence: "/agents/cyclo_intelligence/s6_agent.sock"
|
|
8
11
|
host_agent: "/agents/host/host_agent.sock"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cyclo-manager"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "1.0.0.dev1"
|
|
8
8
|
description = "cyclo_manager CLI: pip-installable launcher for cyclo_manager server and UI containers. Run 'cyclo_manager up' to start Docker stack."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/cyclo_manager.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/cyclo_manager.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cyclo_manager-0.4.0.dev0 → cyclo_manager-1.0.0.dev1}/cyclo_manager_cli/docker/docker-compose.yml
RENAMED
|
File without changes
|
|
File without changes
|