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.
@@ -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,2 @@
1
+ [console_scripts]
2
+ jtop-installer = jtop_installer.cli:main
@@ -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__"}
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+