cyclo-manager 0.2.0.dev0__tar.gz → 0.2.0.dev2__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.dev0 → cyclo_manager-0.2.0.dev2}/PKG-INFO +1 -1
- {cyclo_manager-0.2.0.dev0 → cyclo_manager-0.2.0.dev2}/cyclo_host_agent/routers/repos.py +31 -26
- {cyclo_manager-0.2.0.dev0 → cyclo_manager-0.2.0.dev2}/cyclo_host_agent/routers/update.py +18 -56
- {cyclo_manager-0.2.0.dev0 → cyclo_manager-0.2.0.dev2}/cyclo_manager.egg-info/PKG-INFO +1 -1
- {cyclo_manager-0.2.0.dev0 → cyclo_manager-0.2.0.dev2}/cyclo_manager_cli/cli.py +18 -2
- {cyclo_manager-0.2.0.dev0 → cyclo_manager-0.2.0.dev2}/pyproject.toml +1 -1
- {cyclo_manager-0.2.0.dev0 → cyclo_manager-0.2.0.dev2}/setup.cfg +1 -1
- {cyclo_manager-0.2.0.dev0 → cyclo_manager-0.2.0.dev2}/README.md +0 -0
- {cyclo_manager-0.2.0.dev0 → cyclo_manager-0.2.0.dev2}/cyclo_host_agent/__init__.py +0 -0
- {cyclo_manager-0.2.0.dev0 → cyclo_manager-0.2.0.dev2}/cyclo_host_agent/main.py +0 -0
- {cyclo_manager-0.2.0.dev0 → cyclo_manager-0.2.0.dev2}/cyclo_host_agent/models.py +0 -0
- {cyclo_manager-0.2.0.dev0 → cyclo_manager-0.2.0.dev2}/cyclo_host_agent/routers/__init__.py +0 -0
- {cyclo_manager-0.2.0.dev0 → cyclo_manager-0.2.0.dev2}/cyclo_manager.egg-info/SOURCES.txt +0 -0
- {cyclo_manager-0.2.0.dev0 → cyclo_manager-0.2.0.dev2}/cyclo_manager.egg-info/dependency_links.txt +0 -0
- {cyclo_manager-0.2.0.dev0 → cyclo_manager-0.2.0.dev2}/cyclo_manager.egg-info/entry_points.txt +0 -0
- {cyclo_manager-0.2.0.dev0 → cyclo_manager-0.2.0.dev2}/cyclo_manager.egg-info/requires.txt +0 -0
- {cyclo_manager-0.2.0.dev0 → cyclo_manager-0.2.0.dev2}/cyclo_manager.egg-info/top_level.txt +0 -0
- {cyclo_manager-0.2.0.dev0 → cyclo_manager-0.2.0.dev2}/cyclo_manager_cli/__init__.py +0 -0
- {cyclo_manager-0.2.0.dev0 → cyclo_manager-0.2.0.dev2}/cyclo_manager_cli/config/config.yml +0 -0
- {cyclo_manager-0.2.0.dev0 → cyclo_manager-0.2.0.dev2}/cyclo_manager_cli/docker/docker-compose.yml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cyclo-manager
|
|
3
|
-
Version: 0.2.0.
|
|
3
|
+
Version: 0.2.0.dev2
|
|
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
|
|
@@ -22,6 +22,7 @@ import asyncio
|
|
|
22
22
|
import json
|
|
23
23
|
import os
|
|
24
24
|
import re
|
|
25
|
+
import subprocess
|
|
25
26
|
import urllib.request
|
|
26
27
|
from pathlib import Path
|
|
27
28
|
from typing import Optional
|
|
@@ -86,20 +87,23 @@ def _fmt_cmd(cmd: str, output: str) -> str:
|
|
|
86
87
|
|
|
87
88
|
# ── git helpers ────────────────────────────────────────────────────────────────
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
proc = await asyncio.create_subprocess_exec(
|
|
91
|
-
'git', *args,
|
|
92
|
-
cwd=str(cwd) if cwd else None,
|
|
93
|
-
stdout=asyncio.subprocess.PIPE,
|
|
94
|
-
stderr=asyncio.subprocess.PIPE,
|
|
95
|
-
)
|
|
90
|
+
def _git_sync(args: list[str], cwd: Optional[Path] = None) -> tuple[int, str, str]:
|
|
96
91
|
try:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
92
|
+
result = subprocess.run(
|
|
93
|
+
['git', *args],
|
|
94
|
+
cwd=str(cwd) if cwd else None,
|
|
95
|
+
capture_output=True,
|
|
96
|
+
timeout=GIT_TIMEOUT,
|
|
97
|
+
)
|
|
98
|
+
return result.returncode, result.stdout.decode(), result.stderr.decode()
|
|
99
|
+
except subprocess.TimeoutExpired:
|
|
100
|
+
return 1, '', 'git timed out'
|
|
101
|
+
except Exception as e:
|
|
102
|
+
return 1, '', str(e)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
async def _git(args: list[str], cwd: Optional[Path] = None) -> tuple[int, str, str]:
|
|
106
|
+
return await asyncio.to_thread(_git_sync, args, cwd)
|
|
103
107
|
|
|
104
108
|
|
|
105
109
|
async def _repo_info(repo_path: Path) -> RepoInfo:
|
|
@@ -279,24 +283,25 @@ async def _update_reset(repo_path: Path, preserve_files: list[str]) -> UpdateRes
|
|
|
279
283
|
|
|
280
284
|
# ── container helper ───────────────────────────────────────────────────────────
|
|
281
285
|
|
|
282
|
-
|
|
286
|
+
def _run_container_sh_sync(repo_path: Path, action: str) -> tuple[bool, str]:
|
|
283
287
|
script = repo_path / 'docker' / 'container.sh'
|
|
284
288
|
if not script.exists():
|
|
285
289
|
return False, f'container.sh not found at {script}'
|
|
286
|
-
proc = await asyncio.create_subprocess_exec(
|
|
287
|
-
'bash', str(script), action,
|
|
288
|
-
cwd=str(repo_path / 'docker'),
|
|
289
|
-
stdin=asyncio.subprocess.PIPE,
|
|
290
|
-
stdout=asyncio.subprocess.PIPE,
|
|
291
|
-
stderr=asyncio.subprocess.PIPE,
|
|
292
|
-
)
|
|
293
290
|
try:
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
291
|
+
result = subprocess.run(
|
|
292
|
+
['bash', str(script), action],
|
|
293
|
+
cwd=str(repo_path / 'docker'),
|
|
294
|
+
input=b'y\n',
|
|
295
|
+
capture_output=True,
|
|
296
|
+
timeout=300.0,
|
|
297
|
+
)
|
|
298
|
+
return result.returncode == 0, (result.stdout.decode() + result.stderr.decode()).strip()
|
|
299
|
+
except subprocess.TimeoutExpired:
|
|
298
300
|
return False, 'Timeout waiting for container.sh'
|
|
299
|
-
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
async def _run_container_sh(repo_path: Path, action: str) -> tuple[bool, str]:
|
|
304
|
+
return await asyncio.to_thread(_run_container_sh_sync, repo_path, action)
|
|
300
305
|
|
|
301
306
|
|
|
302
307
|
# ── endpoints ──────────────────────────────────────────────────────────────────
|
|
@@ -42,16 +42,15 @@ def _fmt(cmd: str, out: str) -> str:
|
|
|
42
42
|
return f'$ {cmd}\n{out.strip()}' if out.strip() else f'$ {cmd}'
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
time.sleep(2)
|
|
45
|
+
|
|
46
|
+
def _run_cmd(args: list[str], timeout: float) -> tuple[int, str]:
|
|
48
47
|
try:
|
|
49
|
-
subprocess.run(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
result = subprocess.run(args, capture_output=True, timeout=timeout)
|
|
49
|
+
return result.returncode, (result.stdout.decode() + result.stderr.decode()).strip()
|
|
50
|
+
except subprocess.TimeoutExpired:
|
|
51
|
+
return 1, 'timed out'
|
|
53
52
|
except Exception as e:
|
|
54
|
-
|
|
53
|
+
return 1, str(e)
|
|
55
54
|
|
|
56
55
|
|
|
57
56
|
async def _run_install_and_up(cyclo_exe: str, pip_exe: str) -> None:
|
|
@@ -60,50 +59,24 @@ async def _run_install_and_up(cyclo_exe: str, pip_exe: str) -> None:
|
|
|
60
59
|
|
|
61
60
|
# pip install -U
|
|
62
61
|
_update_status['phase'] = 'installing'
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
stdout=asyncio.subprocess.PIPE,
|
|
67
|
-
stderr=asyncio.subprocess.PIPE,
|
|
68
|
-
)
|
|
69
|
-
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=120)
|
|
70
|
-
out = (stdout.decode() + stderr.decode()).strip()
|
|
71
|
-
_update_status['install_output'] = _fmt(f'pip install -U {PYPI_PACKAGE}', out)
|
|
72
|
-
if proc.returncode != 0:
|
|
73
|
-
_update_status['phase'] = 'error'
|
|
74
|
-
_update_status['error'] = f'pip install failed (exit {proc.returncode})'
|
|
75
|
-
return
|
|
76
|
-
except asyncio.TimeoutError:
|
|
62
|
+
rc, out = await asyncio.to_thread(_run_cmd, [pip_exe, 'install', '-U', PYPI_PACKAGE], 120)
|
|
63
|
+
_update_status['install_output'] = _fmt(f'pip install -U {PYPI_PACKAGE}', out)
|
|
64
|
+
if rc != 0:
|
|
77
65
|
_update_status['phase'] = 'error'
|
|
78
|
-
_update_status['error'] = 'pip install
|
|
66
|
+
_update_status['error'] = f'pip install failed (exit {rc})'
|
|
79
67
|
return
|
|
80
68
|
|
|
81
69
|
# cyclo up
|
|
82
70
|
_update_status['phase'] = 'starting'
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
stdout=asyncio.subprocess.PIPE,
|
|
87
|
-
stderr=asyncio.subprocess.PIPE,
|
|
88
|
-
)
|
|
89
|
-
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=300)
|
|
90
|
-
out = (stdout.decode() + stderr.decode()).strip()
|
|
91
|
-
_update_status['up_output'] = _fmt('cyclo_manager up', out)
|
|
92
|
-
if proc.returncode != 0:
|
|
93
|
-
_update_status['phase'] = 'error'
|
|
94
|
-
_update_status['error'] = f'cyclo_manager up failed (exit {proc.returncode})'
|
|
95
|
-
return
|
|
96
|
-
except asyncio.TimeoutError:
|
|
71
|
+
rc, out = await asyncio.to_thread(_run_cmd, [cyclo_exe, 'up'], 300)
|
|
72
|
+
_update_status['up_output'] = _fmt('cyclo_manager up', out)
|
|
73
|
+
if rc != 0:
|
|
97
74
|
_update_status['phase'] = 'error'
|
|
98
|
-
_update_status['error'] = 'cyclo_manager up
|
|
75
|
+
_update_status['error'] = f'cyclo_manager up failed (exit {rc})'
|
|
99
76
|
return
|
|
100
77
|
|
|
101
78
|
_update_status['phase'] = 'done'
|
|
102
79
|
|
|
103
|
-
# Restart self so new binary takes effect
|
|
104
|
-
loop = asyncio.get_event_loop()
|
|
105
|
-
await loop.run_in_executor(None, _restart_self)
|
|
106
|
-
|
|
107
80
|
|
|
108
81
|
@router.post('/update')
|
|
109
82
|
async def start_update() -> dict:
|
|
@@ -127,20 +100,9 @@ async def start_update() -> dict:
|
|
|
127
100
|
|
|
128
101
|
_update_status = {'phase': 'stopping', 'install_output': '', 'up_output': '', 'error': ''}
|
|
129
102
|
|
|
130
|
-
# cyclo down
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
proc = await asyncio.create_subprocess_exec(
|
|
134
|
-
cyclo_exe, 'down',
|
|
135
|
-
stdout=asyncio.subprocess.PIPE,
|
|
136
|
-
stderr=asyncio.subprocess.PIPE,
|
|
137
|
-
)
|
|
138
|
-
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=30)
|
|
139
|
-
down_output = _fmt('cyclo_manager down', (stdout.decode() + stderr.decode()))
|
|
140
|
-
except asyncio.TimeoutError:
|
|
141
|
-
down_output = '$ cyclo_manager down\n(timed out)'
|
|
142
|
-
except Exception as e:
|
|
143
|
-
down_output = f'$ cyclo_manager down\n(error: {e})'
|
|
103
|
+
# cyclo down
|
|
104
|
+
rc, out = await asyncio.to_thread(_run_cmd, [cyclo_exe, 'down'], 30)
|
|
105
|
+
down_output = _fmt('cyclo_manager down', out)
|
|
144
106
|
|
|
145
107
|
# Continue install + up in background
|
|
146
108
|
asyncio.create_task(_run_install_and_up(cyclo_exe, pip_exe))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cyclo-manager
|
|
3
|
-
Version: 0.2.0.
|
|
3
|
+
Version: 0.2.0.dev2
|
|
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
|
|
@@ -80,7 +80,8 @@ def _create_host_agent() -> int:
|
|
|
80
80
|
)
|
|
81
81
|
return 1
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
current_user = os.environ.get('SUDO_USER') or os.environ.get('USER') or os.getlogin()
|
|
84
|
+
user_home = Path(f'/home/{current_user}') if current_user else Path.home()
|
|
84
85
|
service_content = f"""\
|
|
85
86
|
[Unit]
|
|
86
87
|
Description=Cyclo Host Agent
|
|
@@ -88,6 +89,7 @@ After=network.target
|
|
|
88
89
|
|
|
89
90
|
[Service]
|
|
90
91
|
Type=simple
|
|
92
|
+
User={current_user}
|
|
91
93
|
ExecStart={agent_exe}
|
|
92
94
|
Environment=HOME={user_home}
|
|
93
95
|
Restart=always
|
|
@@ -121,7 +123,12 @@ WantedBy=multi-user.target
|
|
|
121
123
|
|
|
122
124
|
def cmd_up(args: argparse.Namespace) -> int:
|
|
123
125
|
"""Start API + UI; create zenoh and noVNC containers without starting them."""
|
|
124
|
-
if
|
|
126
|
+
if _check_host_agent():
|
|
127
|
+
try:
|
|
128
|
+
subprocess.run(['sudo', 'systemctl', 'restart', f'{HOST_AGENT_SERVICE}.service'], check=True)
|
|
129
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
130
|
+
print('Warning: Failed to restart host agent service.', file=sys.stderr)
|
|
131
|
+
else:
|
|
125
132
|
if _create_host_agent() != 0:
|
|
126
133
|
print('Warning: Failed to install host agent service.')
|
|
127
134
|
|
|
@@ -230,6 +237,15 @@ def cmd_update(args: argparse.Namespace) -> int:
|
|
|
230
237
|
except subprocess.CalledProcessError as e:
|
|
231
238
|
return e.returncode
|
|
232
239
|
|
|
240
|
+
print('Restarting host agent...')
|
|
241
|
+
try:
|
|
242
|
+
subprocess.run(
|
|
243
|
+
['sudo', 'systemctl', 'restart', f'{HOST_AGENT_SERVICE}.service'],
|
|
244
|
+
check=True,
|
|
245
|
+
)
|
|
246
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
247
|
+
print('Warning: Failed to restart host agent service.', file=sys.stderr)
|
|
248
|
+
|
|
233
249
|
print('cyclo_manager update completed.')
|
|
234
250
|
return 0
|
|
235
251
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cyclo-manager"
|
|
7
|
-
version = "0.2.0.
|
|
7
|
+
version = "0.2.0.dev2"
|
|
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.2.0.dev0 → cyclo_manager-0.2.0.dev2}/cyclo_manager.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{cyclo_manager-0.2.0.dev0 → cyclo_manager-0.2.0.dev2}/cyclo_manager.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cyclo_manager-0.2.0.dev0 → cyclo_manager-0.2.0.dev2}/cyclo_manager_cli/docker/docker-compose.yml
RENAMED
|
File without changes
|