cyclo-manager 1.0.0.dev5__tar.gz → 1.0.0.dev7__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 (20) hide show
  1. {cyclo_manager-1.0.0.dev5 → cyclo_manager-1.0.0.dev7}/PKG-INFO +4 -4
  2. {cyclo_manager-1.0.0.dev5 → cyclo_manager-1.0.0.dev7}/README.md +3 -3
  3. {cyclo_manager-1.0.0.dev5 → cyclo_manager-1.0.0.dev7}/cyclo_host_agent/routers/update.py +1 -4
  4. {cyclo_manager-1.0.0.dev5 → cyclo_manager-1.0.0.dev7}/cyclo_manager.egg-info/PKG-INFO +4 -4
  5. {cyclo_manager-1.0.0.dev5 → cyclo_manager-1.0.0.dev7}/cyclo_manager_cli/cli.py +66 -7
  6. {cyclo_manager-1.0.0.dev5 → cyclo_manager-1.0.0.dev7}/pyproject.toml +1 -1
  7. {cyclo_manager-1.0.0.dev5 → cyclo_manager-1.0.0.dev7}/cyclo_host_agent/__init__.py +0 -0
  8. {cyclo_manager-1.0.0.dev5 → cyclo_manager-1.0.0.dev7}/cyclo_host_agent/main.py +0 -0
  9. {cyclo_manager-1.0.0.dev5 → cyclo_manager-1.0.0.dev7}/cyclo_host_agent/models.py +0 -0
  10. {cyclo_manager-1.0.0.dev5 → cyclo_manager-1.0.0.dev7}/cyclo_host_agent/routers/__init__.py +0 -0
  11. {cyclo_manager-1.0.0.dev5 → cyclo_manager-1.0.0.dev7}/cyclo_host_agent/routers/repos.py +0 -0
  12. {cyclo_manager-1.0.0.dev5 → cyclo_manager-1.0.0.dev7}/cyclo_manager.egg-info/SOURCES.txt +0 -0
  13. {cyclo_manager-1.0.0.dev5 → cyclo_manager-1.0.0.dev7}/cyclo_manager.egg-info/dependency_links.txt +0 -0
  14. {cyclo_manager-1.0.0.dev5 → cyclo_manager-1.0.0.dev7}/cyclo_manager.egg-info/entry_points.txt +0 -0
  15. {cyclo_manager-1.0.0.dev5 → cyclo_manager-1.0.0.dev7}/cyclo_manager.egg-info/requires.txt +0 -0
  16. {cyclo_manager-1.0.0.dev5 → cyclo_manager-1.0.0.dev7}/cyclo_manager.egg-info/top_level.txt +0 -0
  17. {cyclo_manager-1.0.0.dev5 → cyclo_manager-1.0.0.dev7}/cyclo_manager_cli/__init__.py +0 -0
  18. {cyclo_manager-1.0.0.dev5 → cyclo_manager-1.0.0.dev7}/cyclo_manager_cli/config/config.yml +0 -0
  19. {cyclo_manager-1.0.0.dev5 → cyclo_manager-1.0.0.dev7}/cyclo_manager_cli/docker/docker-compose.yml +0 -0
  20. {cyclo_manager-1.0.0.dev5 → cyclo_manager-1.0.0.dev7}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cyclo-manager
3
- Version: 1.0.0.dev5
3
+ Version: 1.0.0.dev7
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
@@ -120,16 +120,16 @@ 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; skipped on root-only devices) |
123
+ | sudoers | `/etc/sudoers.d/cyclo_manager` (`cp`, `udevadm` for container setup scripts; `refresh-host-agent` CLI subcommand for UI updates; 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
 
127
127
  **Responsibilities:**
128
128
 
129
129
  - List and update **`~/ROBOTIS-GIT/*`** git repositories (Version Management in the UI)
130
- - Run **`cyclo_manager update`** when triggered from the UI (`POST /host/update`)
130
+ - Run Cyclo Manager package updates from the UI by delegating to `cyclo_manager update`
131
131
 
132
- `cyclo_manager up` and `cyclo_manager update` both call `_ensure_host_agent()`, which is idempotent — safe to run after upgrades or user changes.
132
+ `cyclo_manager up` installs the `refresh-host-agent` CLI subcommand into sudoers. It is registered as `sys.executable -m cyclo_manager_cli.cli refresh-host-agent` with `SETENV`, so UI-triggered updates can pass the current `PYTHONPATH` to sudo and do not depend on `PATH`. The command is idempotent and refreshes the socket directory, sudoers file, systemd unit, and service state after package upgrades.
133
133
 
134
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
135
 
@@ -106,16 +106,16 @@ 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; skipped on root-only devices) |
109
+ | sudoers | `/etc/sudoers.d/cyclo_manager` (`cp`, `udevadm` for container setup scripts; `refresh-host-agent` CLI subcommand for UI updates; 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
 
113
113
  **Responsibilities:**
114
114
 
115
115
  - List and update **`~/ROBOTIS-GIT/*`** git repositories (Version Management in the UI)
116
- - Run **`cyclo_manager update`** when triggered from the UI (`POST /host/update`)
116
+ - Run Cyclo Manager package updates from the UI by delegating to `cyclo_manager update`
117
117
 
118
- `cyclo_manager up` and `cyclo_manager update` both call `_ensure_host_agent()`, which is idempotent — safe to run after upgrades or user changes.
118
+ `cyclo_manager up` installs the `refresh-host-agent` CLI subcommand into sudoers. It is registered as `sys.executable -m cyclo_manager_cli.cli refresh-host-agent` with `SETENV`, so UI-triggered updates can pass the current `PYTHONPATH` to sudo and do not depend on `PATH`. The command is idempotent and refreshes the socket directory, sudoers file, systemd unit, and service state after package upgrades.
119
119
 
120
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
121
 
@@ -19,13 +19,10 @@
19
19
  """System-level endpoints for the host agent."""
20
20
 
21
21
  import asyncio
22
- import logging
23
22
  import sys
24
23
 
25
24
  from fastapi import APIRouter, HTTPException, status
26
25
 
27
- logger = logging.getLogger(__name__)
28
-
29
26
  router = APIRouter(prefix='/system', tags=['system'])
30
27
 
31
28
  _update_status: dict = {'phase': 'idle', 'output': '', 'error': ''}
@@ -57,7 +54,7 @@ async def _run_update() -> None:
57
54
 
58
55
  @router.post('/update')
59
56
  async def start_update() -> dict:
60
- """Delegate update to cyclo_manager update, which handles containers and host agent restart."""
57
+ """Run cyclo_manager update in the background."""
61
58
  global _update_status
62
59
 
63
60
  if _update_status.get('phase') == 'updating':
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cyclo-manager
3
- Version: 1.0.0.dev5
3
+ Version: 1.0.0.dev7
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
@@ -120,16 +120,16 @@ 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; skipped on root-only devices) |
123
+ | sudoers | `/etc/sudoers.d/cyclo_manager` (`cp`, `udevadm` for container setup scripts; `refresh-host-agent` CLI subcommand for UI updates; 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
 
127
127
  **Responsibilities:**
128
128
 
129
129
  - List and update **`~/ROBOTIS-GIT/*`** git repositories (Version Management in the UI)
130
- - Run **`cyclo_manager update`** when triggered from the UI (`POST /host/update`)
130
+ - Run Cyclo Manager package updates from the UI by delegating to `cyclo_manager update`
131
131
 
132
- `cyclo_manager up` and `cyclo_manager update` both call `_ensure_host_agent()`, which is idempotent — safe to run after upgrades or user changes.
132
+ `cyclo_manager up` installs the `refresh-host-agent` CLI subcommand into sudoers. It is registered as `sys.executable -m cyclo_manager_cli.cli refresh-host-agent` with `SETENV`, so UI-triggered updates can pass the current `PYTHONPATH` to sudo and do not depend on `PATH`. The command is idempotent and refreshes the socket directory, sudoers file, systemd unit, and service state after package upgrades.
133
133
 
134
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
135
 
@@ -160,14 +160,23 @@ def _install_sudoers(user: str) -> bool:
160
160
  """
161
161
  Write sudoers rules for cyclo_manager.
162
162
 
163
- Grant NOPASSWD for udev operations needed by container.sh
164
- (cp to /etc/udev/rules.d/, udevadm control/trigger).
163
+ Grant NOPASSWD for udev operations and the root-only host-agent refresh command.
165
164
  """
166
- print(' Installing sudoers rules (cp, udevadm)...')
165
+ if not sys.executable:
166
+ print(
167
+ 'Python executable is unknown. '
168
+ 'Run `pip install cyclo-manager` and retry.',
169
+ file=sys.stderr,
170
+ )
171
+ return False
172
+
173
+ print(' Installing sudoers rules (cp, udevadm, host-agent refresh command)...')
167
174
  sudoers_content = (
168
175
  f'{user} ALL=(ALL) NOPASSWD: /usr/bin/cp, '
169
176
  f'/usr/bin/udevadm control --reload-rules, '
170
- f'/usr/bin/udevadm trigger\n'
177
+ f'/usr/bin/udevadm trigger, '
178
+ 'SETENV: '
179
+ f'{sys.executable} -m cyclo_manager_cli.cli refresh-host-agent\n'
171
180
  )
172
181
  sudoers_file = '/etc/sudoers.d/cyclo_manager'
173
182
  try:
@@ -188,7 +197,7 @@ def _install_sudoers(user: str) -> bool:
188
197
  return False
189
198
 
190
199
 
191
- def _ensure_host_agent() -> int:
200
+ def _ensure_host_agent(restart_no_block: bool = False) -> int:
192
201
  """
193
202
  Install or refresh cyclo_host_agent: systemd unit, socket dir, and service start.
194
203
 
@@ -261,8 +270,11 @@ WantedBy=multi-user.target
261
270
  capture_output=True,
262
271
  )
263
272
  print(f' Starting {HOST_AGENT_SERVICE}...')
273
+ restart_cmd = ['systemctl', 'restart', f'{HOST_AGENT_SERVICE}.service']
274
+ if restart_no_block:
275
+ restart_cmd.insert(1, '--no-block')
264
276
  subprocess.run(
265
- _sudo_cmd(['systemctl', 'restart', f'{HOST_AGENT_SERVICE}.service']),
277
+ _sudo_cmd(restart_cmd),
266
278
  check=True,
267
279
  capture_output=True,
268
280
  )
@@ -276,6 +288,47 @@ WantedBy=multi-user.target
276
288
  return 1
277
289
 
278
290
 
291
+ def _refresh_host_agent_after_update() -> int:
292
+ """Refresh host agent with the sudoers-registered cyclo_manager command when possible."""
293
+ if os.geteuid() == 0:
294
+ return _ensure_host_agent(restart_no_block=True)
295
+ if not sys.executable:
296
+ print(
297
+ 'Python executable is unknown; falling back to setup.',
298
+ file=sys.stderr,
299
+ )
300
+ return _ensure_host_agent() if sys.stdin.isatty() else 1
301
+
302
+ refresh_command = [sys.executable, '-m', 'cyclo_manager_cli.cli', 'refresh-host-agent']
303
+ print(f'Refreshing host agent with sudo command: {" ".join(refresh_command)}')
304
+ pythonpath = os.pathsep.join(dict.fromkeys(path for path in sys.path if path))
305
+ try:
306
+ subprocess.run(['sudo', '-n', f'PYTHONPATH={pythonpath}', *refresh_command], check=True)
307
+ return 0
308
+ except subprocess.CalledProcessError as e:
309
+ if sys.stdin.isatty():
310
+ print(
311
+ 'Warning: sudo refresh command failed; falling back to interactive setup.',
312
+ file=sys.stderr,
313
+ )
314
+ return _ensure_host_agent()
315
+ print(f'Host-agent refresh command failed: {e}', file=sys.stderr)
316
+ return e.returncode
317
+ except FileNotFoundError:
318
+ if sys.stdin.isatty():
319
+ return _ensure_host_agent()
320
+ print('sudo not found; cannot refresh host agent service.', file=sys.stderr)
321
+ return 1
322
+
323
+
324
+ def cmd_refresh_host_agent(args: argparse.Namespace) -> int:
325
+ """Refresh the host agent from sudoers after a package update."""
326
+ if os.geteuid() != 0:
327
+ print('cyclo_manager refresh-host-agent must run as root.', file=sys.stderr)
328
+ return 1
329
+ return _ensure_host_agent(restart_no_block=True)
330
+
331
+
279
332
  def cmd_up(args: argparse.Namespace) -> int:
280
333
  """Start API + UI; create zenoh and noVNC containers without starting them."""
281
334
  print('--- cyclo_manager up ---')
@@ -445,7 +498,7 @@ def cmd_update(args: argparse.Namespace) -> int:
445
498
  return e.returncode
446
499
 
447
500
  print('Refreshing host agent (unit, socket, sudoers)...')
448
- if _ensure_host_agent() != 0:
501
+ if _refresh_host_agent_after_update() != 0:
449
502
  print('Warning: Failed to refresh host agent service.', file=sys.stderr)
450
503
  return 1
451
504
 
@@ -486,6 +539,12 @@ def main() -> int:
486
539
  )
487
540
  update_parser.set_defaults(func=cmd_update)
488
541
 
542
+ refresh_parser = sub.add_parser(
543
+ 'refresh-host-agent',
544
+ help='Refresh cyclo_host_agent service after package update',
545
+ )
546
+ refresh_parser.set_defaults(func=cmd_refresh_host_agent)
547
+
489
548
  args = parser.parse_args()
490
549
  if not args.command:
491
550
  parser.print_help()
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cyclo-manager"
7
- version = "1.0.0.dev5"
7
+ version = "1.0.0.dev7"
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"