jtop-installer 0.1.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.
- jtop_installer-0.1.0/PKG-INFO +94 -0
- jtop_installer-0.1.0/README.md +77 -0
- jtop_installer-0.1.0/jtop_installer/__init__.py +1 -0
- jtop_installer-0.1.0/jtop_installer/cli.py +60 -0
- jtop_installer-0.1.0/jtop_installer/core.py +328 -0
- jtop_installer-0.1.0/jtop_installer.egg-info/PKG-INFO +94 -0
- jtop_installer-0.1.0/jtop_installer.egg-info/SOURCES.txt +10 -0
- jtop_installer-0.1.0/jtop_installer.egg-info/dependency_links.txt +1 -0
- jtop_installer-0.1.0/jtop_installer.egg-info/entry_points.txt +2 -0
- jtop_installer-0.1.0/jtop_installer.egg-info/top_level.txt +1 -0
- jtop_installer-0.1.0/pyproject.toml +36 -0
- jtop_installer-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jtop-installer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Bootstrap installer for jetson-stats (jtop): isolated uv venv, system-wide symlink, and systemd service — never touches system site-packages
|
|
5
|
+
Author-email: Raffaello Bonghi <raffaello@rnext.it>
|
|
6
|
+
License-Expression: AGPL-3.0-or-later
|
|
7
|
+
Project-URL: Repository, https://github.com/rbonghi/jetson_stats
|
|
8
|
+
Project-URL: Issues, https://github.com/rbonghi/jetson_stats/issues
|
|
9
|
+
Keywords: jetson_stats,jtop,installer,nvidia,Jetson
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Topic :: System :: Installation/Setup
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# jtop-installer
|
|
19
|
+
|
|
20
|
+
Bootstrap installer for [jetson-stats](https://github.com/rbonghi/jetson_stats) (`jtop`).
|
|
21
|
+
|
|
22
|
+
It installs jtop into an **isolated uv venv** at `~/.local/share/jtop` — never
|
|
23
|
+
into system site-packages — so it works on externally managed Pythons without
|
|
24
|
+
`--break-system-packages`. System-wide it only creates:
|
|
25
|
+
|
|
26
|
+
- a symlink: `/usr/local/bin/jtop` → `~/.local/share/jtop/bin/jtop`
|
|
27
|
+
- a systemd unit: `/etc/systemd/system/jtop.service`
|
|
28
|
+
|
|
29
|
+
This is a pure-Python replacement for the former
|
|
30
|
+
`install_jtop_torun_without_sudo.sh` and `upgrade-jtop.sh` scripts.
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
Run as a **regular user** (not with sudo — it invokes sudo itself when needed;
|
|
35
|
+
run `sudo -v` first if you prefer to prime credentials).
|
|
36
|
+
|
|
37
|
+
With [uv](https://docs.astral.sh/uv/) installed:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Fresh install
|
|
41
|
+
uvx jtop-installer install
|
|
42
|
+
|
|
43
|
+
# Upgrade (also removes any legacy system-wide jtop installs)
|
|
44
|
+
uvx jtop-installer upgrade
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or with pipx:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pipx run jtop-installer install
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
On a machine without uv/pipx, run it straight from the repo with system Python
|
|
54
|
+
(stdlib only, no dependencies):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
python3 -m jtop_installer.cli install
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
(from this directory; `install` will bootstrap uv itself if it is missing.)
|
|
61
|
+
|
|
62
|
+
### Options
|
|
63
|
+
|
|
64
|
+
- `--ref REQUIREMENT` — what to install, e.g. `jetson-stats` (PyPI) or a git
|
|
65
|
+
URL/branch. Defaults to `git+https://github.com/rbonghi/jetson_stats.git`.
|
|
66
|
+
Also settable via the `JTOP_REF` environment variable.
|
|
67
|
+
- `install -p / --python VERSION` — Python version for the venv (default `3.12`;
|
|
68
|
+
uv downloads it if not present).
|
|
69
|
+
|
|
70
|
+
## What `install` does
|
|
71
|
+
|
|
72
|
+
1. Bootstraps `uv` if missing (downloads the official installer via stdlib urllib).
|
|
73
|
+
2. Ensures the `jtop` group exists.
|
|
74
|
+
3. Creates the venv at `~/.local/share/jtop` (`uv venv -p 3.12 --seed`).
|
|
75
|
+
4. Installs/upgrades jetson-stats into it.
|
|
76
|
+
5. Optionally symlinks NVIDIA's proprietary `pylibjetsonpower` (Thor) from
|
|
77
|
+
system dist-packages into the venv, if present.
|
|
78
|
+
6. Creates the `/usr/local/bin/jtop` symlink and the `jtop.service` systemd
|
|
79
|
+
unit, then enables and starts the service.
|
|
80
|
+
|
|
81
|
+
## What `upgrade` does
|
|
82
|
+
|
|
83
|
+
1. Removes legacy system-wide jtop installs (pip uninstall from every system
|
|
84
|
+
Python, then force-removes leftovers) — the venv is untouched by this step.
|
|
85
|
+
2. Falls back to a full `install` if uv or the venv is missing/broken.
|
|
86
|
+
3. Stops the service, clears root-owned `__pycache__` dirs in the venv,
|
|
87
|
+
force-reinstalls jetson-stats, refreshes the symlink, restarts the service.
|
|
88
|
+
|
|
89
|
+
## Building / publishing
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
uv build # produces dist/*.whl and dist/*.tar.gz
|
|
93
|
+
uv publish # upload to PyPI (requires credentials)
|
|
94
|
+
```
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# jtop-installer
|
|
2
|
+
|
|
3
|
+
Bootstrap installer for [jetson-stats](https://github.com/rbonghi/jetson_stats) (`jtop`).
|
|
4
|
+
|
|
5
|
+
It installs jtop into an **isolated uv venv** at `~/.local/share/jtop` — never
|
|
6
|
+
into system site-packages — so it works on externally managed Pythons without
|
|
7
|
+
`--break-system-packages`. System-wide it only creates:
|
|
8
|
+
|
|
9
|
+
- a symlink: `/usr/local/bin/jtop` → `~/.local/share/jtop/bin/jtop`
|
|
10
|
+
- a systemd unit: `/etc/systemd/system/jtop.service`
|
|
11
|
+
|
|
12
|
+
This is a pure-Python replacement for the former
|
|
13
|
+
`install_jtop_torun_without_sudo.sh` and `upgrade-jtop.sh` scripts.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
Run as a **regular user** (not with sudo — it invokes sudo itself when needed;
|
|
18
|
+
run `sudo -v` first if you prefer to prime credentials).
|
|
19
|
+
|
|
20
|
+
With [uv](https://docs.astral.sh/uv/) installed:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Fresh install
|
|
24
|
+
uvx jtop-installer install
|
|
25
|
+
|
|
26
|
+
# Upgrade (also removes any legacy system-wide jtop installs)
|
|
27
|
+
uvx jtop-installer upgrade
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or with pipx:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pipx run jtop-installer install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
On a machine without uv/pipx, run it straight from the repo with system Python
|
|
37
|
+
(stdlib only, no dependencies):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
python3 -m jtop_installer.cli install
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
(from this directory; `install` will bootstrap uv itself if it is missing.)
|
|
44
|
+
|
|
45
|
+
### Options
|
|
46
|
+
|
|
47
|
+
- `--ref REQUIREMENT` — what to install, e.g. `jetson-stats` (PyPI) or a git
|
|
48
|
+
URL/branch. Defaults to `git+https://github.com/rbonghi/jetson_stats.git`.
|
|
49
|
+
Also settable via the `JTOP_REF` environment variable.
|
|
50
|
+
- `install -p / --python VERSION` — Python version for the venv (default `3.12`;
|
|
51
|
+
uv downloads it if not present).
|
|
52
|
+
|
|
53
|
+
## What `install` does
|
|
54
|
+
|
|
55
|
+
1. Bootstraps `uv` if missing (downloads the official installer via stdlib urllib).
|
|
56
|
+
2. Ensures the `jtop` group exists.
|
|
57
|
+
3. Creates the venv at `~/.local/share/jtop` (`uv venv -p 3.12 --seed`).
|
|
58
|
+
4. Installs/upgrades jetson-stats into it.
|
|
59
|
+
5. Optionally symlinks NVIDIA's proprietary `pylibjetsonpower` (Thor) from
|
|
60
|
+
system dist-packages into the venv, if present.
|
|
61
|
+
6. Creates the `/usr/local/bin/jtop` symlink and the `jtop.service` systemd
|
|
62
|
+
unit, then enables and starts the service.
|
|
63
|
+
|
|
64
|
+
## What `upgrade` does
|
|
65
|
+
|
|
66
|
+
1. Removes legacy system-wide jtop installs (pip uninstall from every system
|
|
67
|
+
Python, then force-removes leftovers) — the venv is untouched by this step.
|
|
68
|
+
2. Falls back to a full `install` if uv or the venv is missing/broken.
|
|
69
|
+
3. Stops the service, clears root-owned `__pycache__` dirs in the venv,
|
|
70
|
+
force-reinstalls jetson-stats, refreshes the symlink, restarts the service.
|
|
71
|
+
|
|
72
|
+
## Building / publishing
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
uv build # produces dist/*.whl and dist/*.tar.gz
|
|
76
|
+
uv publish # upload to PyPI (requires credentials)
|
|
77
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from . import __version__
|
|
6
|
+
from .core import DEFAULT_PYTHON, DEFAULT_REF, InstallerError, install, upgrade
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main(argv=None):
|
|
10
|
+
parser = argparse.ArgumentParser(
|
|
11
|
+
prog="jtop-installer",
|
|
12
|
+
description=(
|
|
13
|
+
"Install or upgrade jetson-stats (jtop) into an isolated uv venv "
|
|
14
|
+
"at ~/.local/share/jtop, with a /usr/local/bin/jtop symlink and a "
|
|
15
|
+
"systemd service. Never writes to system site-packages."
|
|
16
|
+
),
|
|
17
|
+
)
|
|
18
|
+
parser.add_argument("--version", action="version", version=__version__)
|
|
19
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
20
|
+
|
|
21
|
+
parser_install = subparsers.add_parser(
|
|
22
|
+
"install", help="Create the jtop venv, symlink, and systemd service."
|
|
23
|
+
)
|
|
24
|
+
parser_install.add_argument(
|
|
25
|
+
"-p", "--python",
|
|
26
|
+
default=DEFAULT_PYTHON,
|
|
27
|
+
help="Python version for the venv (default: %(default)s).",
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
parser_upgrade = subparsers.add_parser(
|
|
31
|
+
"upgrade",
|
|
32
|
+
help="Upgrade jetson-stats in the existing venv (removes legacy system installs).",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
for sub in (parser_install, parser_upgrade):
|
|
36
|
+
sub.add_argument(
|
|
37
|
+
"--ref",
|
|
38
|
+
default=os.environ.get("JTOP_REF", DEFAULT_REF),
|
|
39
|
+
help=(
|
|
40
|
+
"pip requirement to install, e.g. 'jetson-stats' or a git URL. "
|
|
41
|
+
"Also settable via the JTOP_REF environment variable "
|
|
42
|
+
"(default: %(default)s)."
|
|
43
|
+
),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
args = parser.parse_args(argv)
|
|
47
|
+
try:
|
|
48
|
+
if args.command == "install":
|
|
49
|
+
return install(ref=args.ref, python=args.python)
|
|
50
|
+
return upgrade(ref=args.ref)
|
|
51
|
+
except InstallerError as error:
|
|
52
|
+
print("ERROR: {}".format(error), file=sys.stderr)
|
|
53
|
+
return 1
|
|
54
|
+
except KeyboardInterrupt:
|
|
55
|
+
print("\nInterrupted.", file=sys.stderr)
|
|
56
|
+
return 130
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
sys.exit(main())
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""Install/upgrade jetson-stats (jtop) into an isolated uv venv.
|
|
2
|
+
|
|
3
|
+
The venv lives in the user's home directory; only a symlink and a systemd
|
|
4
|
+
unit are placed system-wide (via sudo). System site-packages are never
|
|
5
|
+
written to, so there is no need for --break-system-packages.
|
|
6
|
+
"""
|
|
7
|
+
import glob
|
|
8
|
+
import os
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
import urllib.request
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
APP_NAME = "jtop"
|
|
15
|
+
PKG_NAME = "jetson_stats"
|
|
16
|
+
VENV_DIR = Path.home() / ".local" / "share" / APP_NAME
|
|
17
|
+
JTOP_BIN = VENV_DIR / "bin" / APP_NAME
|
|
18
|
+
JTOP_PYTHON = VENV_DIR / "bin" / "python"
|
|
19
|
+
SYMLINK_PATH = Path("/usr/local/bin") / APP_NAME
|
|
20
|
+
SYSTEMD_SERVICE = APP_NAME + ".service"
|
|
21
|
+
SYSTEMD_UNIT = Path("/etc/systemd/system") / SYSTEMD_SERVICE
|
|
22
|
+
DEFAULT_REF = "git+https://github.com/rbonghi/jetson_stats.git"
|
|
23
|
+
DEFAULT_PYTHON = "3.12"
|
|
24
|
+
UV_INSTALL_URL = "https://astral.sh/uv/install.sh"
|
|
25
|
+
|
|
26
|
+
SYSTEMD_UNIT_CONTENT = """\
|
|
27
|
+
[Unit]
|
|
28
|
+
Description=Jetson Stats (jtop service)
|
|
29
|
+
After=network.target
|
|
30
|
+
|
|
31
|
+
[Service]
|
|
32
|
+
Environment="JTOP_SERVICE=True"
|
|
33
|
+
ExecStart={exec_start} --force
|
|
34
|
+
Restart=on-failure
|
|
35
|
+
RestartSec=10s
|
|
36
|
+
TimeoutStartSec=30s
|
|
37
|
+
TimeoutStopSec=30s
|
|
38
|
+
|
|
39
|
+
[Install]
|
|
40
|
+
WantedBy=multi-user.target
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class InstallerError(Exception):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def run(cmd, sudo=False, check=True, capture=False, input_bytes=None):
|
|
49
|
+
cmd = [str(c) for c in cmd]
|
|
50
|
+
if sudo:
|
|
51
|
+
cmd = ["sudo"] + cmd
|
|
52
|
+
result = subprocess.run(
|
|
53
|
+
cmd,
|
|
54
|
+
check=False,
|
|
55
|
+
stdout=subprocess.PIPE if capture else None,
|
|
56
|
+
input=input_bytes,
|
|
57
|
+
)
|
|
58
|
+
if check and result.returncode != 0:
|
|
59
|
+
raise InstallerError(
|
|
60
|
+
"command failed with exit code {}: {}".format(result.returncode, " ".join(cmd))
|
|
61
|
+
)
|
|
62
|
+
return result.stdout.decode() if capture else ""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def ensure_not_root():
|
|
66
|
+
if os.geteuid() == 0:
|
|
67
|
+
raise InstallerError(
|
|
68
|
+
"Run this command as a regular user, NOT with sudo.\n"
|
|
69
|
+
"It will invoke sudo itself where needed (run 'sudo -v' first if you prefer)."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def uv_path():
|
|
74
|
+
found = shutil.which("uv")
|
|
75
|
+
if found:
|
|
76
|
+
return found
|
|
77
|
+
for candidate in (Path.home() / ".local" / "bin" / "uv", Path.home() / ".cargo" / "bin" / "uv"):
|
|
78
|
+
if candidate.is_file() and os.access(str(candidate), os.X_OK):
|
|
79
|
+
return str(candidate)
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def uv_self_update(uv):
|
|
84
|
+
# Fails harmlessly when uv is not managed by the standalone installer
|
|
85
|
+
# (e.g. installed via pip or apt), so don't abort on error.
|
|
86
|
+
print("Updating 'uv'...")
|
|
87
|
+
run([uv, "self", "update", "-q"], check=False)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def ensure_uv():
|
|
91
|
+
uv = uv_path()
|
|
92
|
+
if uv:
|
|
93
|
+
print("'uv' is already installed.")
|
|
94
|
+
uv_self_update(uv)
|
|
95
|
+
return uv
|
|
96
|
+
print("Installing 'uv' (an exceptionally fast Python package installer)...")
|
|
97
|
+
with urllib.request.urlopen(UV_INSTALL_URL) as response:
|
|
98
|
+
script = response.read()
|
|
99
|
+
run(["sh"], input_bytes=script)
|
|
100
|
+
uv = uv_path()
|
|
101
|
+
if not uv:
|
|
102
|
+
raise InstallerError("uv installation failed: 'uv' not found after install.")
|
|
103
|
+
return uv
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def jtop_service_exists():
|
|
107
|
+
out = run(
|
|
108
|
+
["systemctl", "list-unit-files", SYSTEMD_SERVICE, "--no-legend"],
|
|
109
|
+
capture=True,
|
|
110
|
+
check=False,
|
|
111
|
+
)
|
|
112
|
+
return any(line.startswith(SYSTEMD_SERVICE) for line in out.splitlines())
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def link_pylibjetsonpower():
|
|
116
|
+
"""Make proprietary pylibjetsonpower visible inside the venv (Thor).
|
|
117
|
+
|
|
118
|
+
Not required for upstream jetson_stats; a local convenience so the
|
|
119
|
+
service (running from this venv) can import pylibjetsonpower if present.
|
|
120
|
+
"""
|
|
121
|
+
print("Checking for NVIDIA pylibjetsonpower (optional)...")
|
|
122
|
+
patterns = [
|
|
123
|
+
"/usr/lib/python3/dist-packages/pylibjetsonpower",
|
|
124
|
+
"/usr/local/lib/python*/dist-packages/pylibjetsonpower",
|
|
125
|
+
"/usr/lib/python*/dist-packages/pylibjetsonpower",
|
|
126
|
+
]
|
|
127
|
+
source = None
|
|
128
|
+
for pattern in patterns:
|
|
129
|
+
for path in sorted(glob.glob(pattern)):
|
|
130
|
+
if os.path.isfile(os.path.join(path, "__init__.py")):
|
|
131
|
+
source = path
|
|
132
|
+
break
|
|
133
|
+
if source:
|
|
134
|
+
break
|
|
135
|
+
if not source:
|
|
136
|
+
print(" pylibjetsonpower not found (skipping).")
|
|
137
|
+
return
|
|
138
|
+
purelib = run(
|
|
139
|
+
[JTOP_PYTHON, "-c", "import sysconfig; print(sysconfig.get_paths()['purelib'])"],
|
|
140
|
+
capture=True,
|
|
141
|
+
).strip()
|
|
142
|
+
dest = Path(purelib) / "pylibjetsonpower"
|
|
143
|
+
print(" Found: {}".format(source))
|
|
144
|
+
print(" Linking into venv: {}".format(dest))
|
|
145
|
+
if dest.is_symlink() or dest.is_file():
|
|
146
|
+
dest.unlink()
|
|
147
|
+
elif dest.is_dir():
|
|
148
|
+
shutil.rmtree(str(dest))
|
|
149
|
+
dest.symlink_to(source)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def write_systemd_unit():
|
|
153
|
+
content = SYSTEMD_UNIT_CONTENT.format(exec_start=SYMLINK_PATH)
|
|
154
|
+
run(["tee", SYSTEMD_UNIT], sudo=True, capture=True, input_bytes=content.encode())
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _find_system_dirs(name):
|
|
158
|
+
out = run(
|
|
159
|
+
[
|
|
160
|
+
"find", "/usr/lib", "/usr/local/lib",
|
|
161
|
+
"-type", "d",
|
|
162
|
+
"-name", name,
|
|
163
|
+
"-path", "*/python3*/*-packages/*",
|
|
164
|
+
"-prune", "-print",
|
|
165
|
+
],
|
|
166
|
+
sudo=True,
|
|
167
|
+
capture=True,
|
|
168
|
+
check=False,
|
|
169
|
+
)
|
|
170
|
+
venv_prefix = str(VENV_DIR) + os.sep
|
|
171
|
+
return [line for line in out.splitlines() if line and not line.startswith(venv_prefix)]
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def remove_legacy_jtop():
|
|
175
|
+
"""Remove legacy system-wide jtop installs outside the uv venv."""
|
|
176
|
+
print("Checking for legacy system jtop installs outside the uv venv...")
|
|
177
|
+
legacy_dirs = _find_system_dirs(APP_NAME)
|
|
178
|
+
if not legacy_dirs:
|
|
179
|
+
print("No legacy jtop installs found.")
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
print("Found legacy jtop directories:")
|
|
183
|
+
for path in legacy_dirs:
|
|
184
|
+
print(" {}".format(path))
|
|
185
|
+
|
|
186
|
+
print("Attempting to uninstall legacy system jtop with system Python tools...")
|
|
187
|
+
pythons = sorted(
|
|
188
|
+
set(
|
|
189
|
+
glob.glob("/usr/bin/python3")
|
|
190
|
+
+ glob.glob("/usr/bin/python3.[0-9]*")
|
|
191
|
+
+ glob.glob("/usr/local/bin/python3")
|
|
192
|
+
+ glob.glob("/usr/local/bin/python3.[0-9]*")
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
for py in pythons:
|
|
196
|
+
if not os.access(py, os.X_OK):
|
|
197
|
+
continue
|
|
198
|
+
print("Trying: sudo {} -m pip uninstall -y jetson-stats jetson_stats".format(py))
|
|
199
|
+
result = subprocess.run(
|
|
200
|
+
["sudo", py, "-m", "pip", "uninstall", "-y", "jetson-stats", "jetson_stats"]
|
|
201
|
+
)
|
|
202
|
+
if result.returncode != 0:
|
|
203
|
+
print("Retrying with --break-system-packages for externally managed Python...")
|
|
204
|
+
subprocess.run(
|
|
205
|
+
["sudo", py, "-m", "pip", "uninstall", "-y", "--break-system-packages",
|
|
206
|
+
"jetson-stats", "jetson_stats"]
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
print("Rechecking for leftover legacy jtop directories...")
|
|
210
|
+
leftover_dirs = _find_system_dirs(APP_NAME)
|
|
211
|
+
if leftover_dirs:
|
|
212
|
+
print("Force-removing leftover directories:")
|
|
213
|
+
for path in leftover_dirs:
|
|
214
|
+
print(" {}".format(path))
|
|
215
|
+
run(["rm", "-rf"] + leftover_dirs, sudo=True)
|
|
216
|
+
else:
|
|
217
|
+
print("All legacy jtop directories removed.")
|
|
218
|
+
|
|
219
|
+
dist_info_dirs = _find_system_dirs("jetson_stats-*.dist-info")
|
|
220
|
+
if dist_info_dirs:
|
|
221
|
+
print("Removing leftover dist-info directories:")
|
|
222
|
+
for path in dist_info_dirs:
|
|
223
|
+
print(" {}".format(path))
|
|
224
|
+
run(["rm", "-rf"] + dist_info_dirs, sudo=True)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def install(ref=DEFAULT_REF, python=DEFAULT_PYTHON):
|
|
228
|
+
ensure_not_root()
|
|
229
|
+
run(["sudo", "-v"])
|
|
230
|
+
|
|
231
|
+
uv = ensure_uv()
|
|
232
|
+
|
|
233
|
+
# To be safe let's make sure group jtop exists.
|
|
234
|
+
run(["groupadd", "-f", APP_NAME], sudo=True)
|
|
235
|
+
|
|
236
|
+
print("Creating Python virtual environment in {}...".format(VENV_DIR))
|
|
237
|
+
run([uv, "venv", VENV_DIR, "-p", python, "--seed"])
|
|
238
|
+
|
|
239
|
+
print("Installing/upgrading {} from: {}".format(PKG_NAME, ref))
|
|
240
|
+
run([uv, "pip", "install", "--python", JTOP_PYTHON, "--upgrade", ref])
|
|
241
|
+
|
|
242
|
+
link_pylibjetsonpower()
|
|
243
|
+
|
|
244
|
+
if not os.access(str(JTOP_BIN), os.X_OK):
|
|
245
|
+
raise InstallerError("Installation failed: '{}' binary not found.".format(JTOP_BIN))
|
|
246
|
+
|
|
247
|
+
# This makes 'jtop' (user) and 'sudo jtop' (root) work correctly.
|
|
248
|
+
run(["ln", "-sf", JTOP_BIN, SYMLINK_PATH], sudo=True)
|
|
249
|
+
print("Symlink created: {}".format(SYMLINK_PATH))
|
|
250
|
+
|
|
251
|
+
if SYSTEMD_UNIT.exists():
|
|
252
|
+
print("Found existing jtop service. It will be overwritten.")
|
|
253
|
+
print("Creating systemd service: {}".format(SYSTEMD_UNIT))
|
|
254
|
+
write_systemd_unit()
|
|
255
|
+
|
|
256
|
+
print("Enabling and starting {} system service".format(APP_NAME))
|
|
257
|
+
run(["systemctl", "daemon-reload"], sudo=True)
|
|
258
|
+
run(["systemctl", "enable", SYSTEMD_SERVICE], sudo=True)
|
|
259
|
+
run(["systemctl", "restart", SYSTEMD_SERVICE], sudo=True)
|
|
260
|
+
run(["systemctl", "status", SYSTEMD_SERVICE, "--no-pager"], sudo=True, check=False)
|
|
261
|
+
|
|
262
|
+
print()
|
|
263
|
+
print("Installation complete!")
|
|
264
|
+
print()
|
|
265
|
+
print("You can now run '{0}' or 'sudo {0}' (privileged).".format(APP_NAME))
|
|
266
|
+
return 0
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def upgrade(ref=DEFAULT_REF):
|
|
270
|
+
ensure_not_root()
|
|
271
|
+
run(["sudo", "-v"])
|
|
272
|
+
|
|
273
|
+
remove_legacy_jtop()
|
|
274
|
+
|
|
275
|
+
uv = uv_path()
|
|
276
|
+
if uv is None or not VENV_DIR.is_dir():
|
|
277
|
+
print("uv or jtop venv not found — running full installer...")
|
|
278
|
+
return install(ref=ref)
|
|
279
|
+
|
|
280
|
+
if not os.access(str(JTOP_PYTHON), os.X_OK):
|
|
281
|
+
print("Broken jtop venv (Python not found): {}".format(JTOP_PYTHON))
|
|
282
|
+
print("Removing and re-running full installer...")
|
|
283
|
+
run(["rm", "-rf", VENV_DIR], sudo=True)
|
|
284
|
+
return install(ref=ref)
|
|
285
|
+
|
|
286
|
+
uv_self_update(uv)
|
|
287
|
+
|
|
288
|
+
if jtop_service_exists():
|
|
289
|
+
print("Stopping {} before upgrade...".format(SYSTEMD_SERVICE))
|
|
290
|
+
run(["systemctl", "stop", SYSTEMD_SERVICE], sudo=True, check=False)
|
|
291
|
+
else:
|
|
292
|
+
print("{} not found; continuing without stopping service.".format(SYSTEMD_SERVICE))
|
|
293
|
+
|
|
294
|
+
# The service runs as root, so __pycache__ inside the venv may be root-owned.
|
|
295
|
+
print("Removing stale __pycache__ directories in {}...".format(VENV_DIR))
|
|
296
|
+
run(
|
|
297
|
+
["find", VENV_DIR, "-type", "d", "-name", "__pycache__", "-prune",
|
|
298
|
+
"-exec", "rm", "-rf", "--", "{}", "+"],
|
|
299
|
+
sudo=True,
|
|
300
|
+
check=False,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
print("Upgrading {} from:".format(PKG_NAME))
|
|
304
|
+
print(" {}".format(ref))
|
|
305
|
+
run([uv, "pip", "install", "--python", JTOP_PYTHON, "--upgrade", "--force-reinstall", ref])
|
|
306
|
+
|
|
307
|
+
if not os.access(str(JTOP_BIN), os.X_OK):
|
|
308
|
+
raise InstallerError("Upgrade failed: jtop binary not found: {}".format(JTOP_BIN))
|
|
309
|
+
|
|
310
|
+
print("Refreshing symlink:")
|
|
311
|
+
print(" {} -> {}".format(SYMLINK_PATH, JTOP_BIN))
|
|
312
|
+
run(["ln", "-sf", JTOP_BIN, SYMLINK_PATH], sudo=True)
|
|
313
|
+
|
|
314
|
+
if jtop_service_exists():
|
|
315
|
+
print("Reloading systemd and restarting {}...".format(SYSTEMD_SERVICE))
|
|
316
|
+
run(["systemctl", "daemon-reload"], sudo=True, check=False)
|
|
317
|
+
result = subprocess.run(["sudo", "systemctl", "restart", SYSTEMD_SERVICE])
|
|
318
|
+
if result.returncode != 0:
|
|
319
|
+
print("WARNING: upgraded jtop, but failed to restart {}.".format(SYSTEMD_SERVICE))
|
|
320
|
+
print("You can try manually with: sudo systemctl restart {}".format(SYSTEMD_SERVICE))
|
|
321
|
+
else:
|
|
322
|
+
print("{} not found; skipping systemd reload/restart.".format(SYSTEMD_SERVICE))
|
|
323
|
+
|
|
324
|
+
print()
|
|
325
|
+
print("Upgrade complete.")
|
|
326
|
+
print("jtop executable: {} -> {}".format(SYMLINK_PATH, JTOP_BIN))
|
|
327
|
+
run([JTOP_BIN, "--version"], check=False)
|
|
328
|
+
return 0
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jtop-installer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Bootstrap installer for jetson-stats (jtop): isolated uv venv, system-wide symlink, and systemd service — never touches system site-packages
|
|
5
|
+
Author-email: Raffaello Bonghi <raffaello@rnext.it>
|
|
6
|
+
License-Expression: AGPL-3.0-or-later
|
|
7
|
+
Project-URL: Repository, https://github.com/rbonghi/jetson_stats
|
|
8
|
+
Project-URL: Issues, https://github.com/rbonghi/jetson_stats/issues
|
|
9
|
+
Keywords: jetson_stats,jtop,installer,nvidia,Jetson
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Topic :: System :: Installation/Setup
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# jtop-installer
|
|
19
|
+
|
|
20
|
+
Bootstrap installer for [jetson-stats](https://github.com/rbonghi/jetson_stats) (`jtop`).
|
|
21
|
+
|
|
22
|
+
It installs jtop into an **isolated uv venv** at `~/.local/share/jtop` — never
|
|
23
|
+
into system site-packages — so it works on externally managed Pythons without
|
|
24
|
+
`--break-system-packages`. System-wide it only creates:
|
|
25
|
+
|
|
26
|
+
- a symlink: `/usr/local/bin/jtop` → `~/.local/share/jtop/bin/jtop`
|
|
27
|
+
- a systemd unit: `/etc/systemd/system/jtop.service`
|
|
28
|
+
|
|
29
|
+
This is a pure-Python replacement for the former
|
|
30
|
+
`install_jtop_torun_without_sudo.sh` and `upgrade-jtop.sh` scripts.
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
Run as a **regular user** (not with sudo — it invokes sudo itself when needed;
|
|
35
|
+
run `sudo -v` first if you prefer to prime credentials).
|
|
36
|
+
|
|
37
|
+
With [uv](https://docs.astral.sh/uv/) installed:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Fresh install
|
|
41
|
+
uvx jtop-installer install
|
|
42
|
+
|
|
43
|
+
# Upgrade (also removes any legacy system-wide jtop installs)
|
|
44
|
+
uvx jtop-installer upgrade
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or with pipx:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pipx run jtop-installer install
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
On a machine without uv/pipx, run it straight from the repo with system Python
|
|
54
|
+
(stdlib only, no dependencies):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
python3 -m jtop_installer.cli install
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
(from this directory; `install` will bootstrap uv itself if it is missing.)
|
|
61
|
+
|
|
62
|
+
### Options
|
|
63
|
+
|
|
64
|
+
- `--ref REQUIREMENT` — what to install, e.g. `jetson-stats` (PyPI) or a git
|
|
65
|
+
URL/branch. Defaults to `git+https://github.com/rbonghi/jetson_stats.git`.
|
|
66
|
+
Also settable via the `JTOP_REF` environment variable.
|
|
67
|
+
- `install -p / --python VERSION` — Python version for the venv (default `3.12`;
|
|
68
|
+
uv downloads it if not present).
|
|
69
|
+
|
|
70
|
+
## What `install` does
|
|
71
|
+
|
|
72
|
+
1. Bootstraps `uv` if missing (downloads the official installer via stdlib urllib).
|
|
73
|
+
2. Ensures the `jtop` group exists.
|
|
74
|
+
3. Creates the venv at `~/.local/share/jtop` (`uv venv -p 3.12 --seed`).
|
|
75
|
+
4. Installs/upgrades jetson-stats into it.
|
|
76
|
+
5. Optionally symlinks NVIDIA's proprietary `pylibjetsonpower` (Thor) from
|
|
77
|
+
system dist-packages into the venv, if present.
|
|
78
|
+
6. Creates the `/usr/local/bin/jtop` symlink and the `jtop.service` systemd
|
|
79
|
+
unit, then enables and starts the service.
|
|
80
|
+
|
|
81
|
+
## What `upgrade` does
|
|
82
|
+
|
|
83
|
+
1. Removes legacy system-wide jtop installs (pip uninstall from every system
|
|
84
|
+
Python, then force-removes leftovers) — the venv is untouched by this step.
|
|
85
|
+
2. Falls back to a full `install` if uv or the venv is missing/broken.
|
|
86
|
+
3. Stops the service, clears root-owned `__pycache__` dirs in the venv,
|
|
87
|
+
force-reinstalls jetson-stats, refreshes the symlink, restarts the service.
|
|
88
|
+
|
|
89
|
+
## Building / publishing
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
uv build # produces dist/*.whl and dist/*.tar.gz
|
|
93
|
+
uv publish # upload to PyPI (requires credentials)
|
|
94
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
jtop_installer/__init__.py
|
|
4
|
+
jtop_installer/cli.py
|
|
5
|
+
jtop_installer/core.py
|
|
6
|
+
jtop_installer.egg-info/PKG-INFO
|
|
7
|
+
jtop_installer.egg-info/SOURCES.txt
|
|
8
|
+
jtop_installer.egg-info/dependency_links.txt
|
|
9
|
+
jtop_installer.egg-info/entry_points.txt
|
|
10
|
+
jtop_installer.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
jtop_installer
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "jtop-installer"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Bootstrap installer for jetson-stats (jtop): isolated uv venv, system-wide symlink, and systemd service — never touches system site-packages"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{name = "Raffaello Bonghi", email = "raffaello@rnext.it"},
|
|
12
|
+
]
|
|
13
|
+
license = "AGPL-3.0-or-later"
|
|
14
|
+
keywords = ["jetson_stats", "jtop", "installer", "nvidia", "Jetson"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Topic :: System :: Installation/Setup",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Operating System :: POSIX :: Linux",
|
|
21
|
+
]
|
|
22
|
+
requires-python = ">=3.8"
|
|
23
|
+
dependencies = []
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Repository = "https://github.com/rbonghi/jetson_stats"
|
|
27
|
+
Issues = "https://github.com/rbonghi/jetson_stats/issues"
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
jtop-installer = "jtop_installer.cli:main"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools]
|
|
33
|
+
packages = ["jtop_installer"]
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.dynamic]
|
|
36
|
+
version = {attr = "jtop_installer.__version__"}
|