runspec-linux 0.4.0__tar.gz → 0.4.2__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.
- {runspec_linux-0.4.0 → runspec_linux-0.4.2}/CHANGELOG.md +36 -0
- {runspec_linux-0.4.0 → runspec_linux-0.4.2}/PKG-INFO +2 -2
- {runspec_linux-0.4.0 → runspec_linux-0.4.2}/pyproject.toml +5 -2
- {runspec_linux-0.4.0 → runspec_linux-0.4.2}/runspec_linux/__init__.py +6 -0
- {runspec_linux-0.4.0 → runspec_linux-0.4.2}/runspec_linux/runspec.toml +189 -1
- runspec_linux-0.4.2/runspec_linux/venvs.py +81 -0
- {runspec_linux-0.4.0 → runspec_linux-0.4.2}/.gitignore +0 -0
- {runspec_linux-0.4.0 → runspec_linux-0.4.2}/runspec_linux/containers.py +0 -0
- {runspec_linux-0.4.0 → runspec_linux-0.4.2}/runspec_linux/files.py +0 -0
- {runspec_linux-0.4.0 → runspec_linux-0.4.2}/runspec_linux/logs.py +0 -0
- {runspec_linux-0.4.0 → runspec_linux-0.4.2}/runspec_linux/nc_command.py +0 -0
- {runspec_linux-0.4.0 → runspec_linux-0.4.2}/runspec_linux/network.py +0 -0
- {runspec_linux-0.4.0 → runspec_linux-0.4.2}/runspec_linux/packages.py +0 -0
- {runspec_linux-0.4.0 → runspec_linux-0.4.2}/runspec_linux/power.py +0 -0
- {runspec_linux-0.4.0 → runspec_linux-0.4.2}/runspec_linux/security.py +0 -0
- {runspec_linux-0.4.0 → runspec_linux-0.4.2}/runspec_linux/services.py +0 -0
- {runspec_linux-0.4.0 → runspec_linux-0.4.2}/runspec_linux/sudoers.py +0 -0
- {runspec_linux-0.4.0 → runspec_linux-0.4.2}/runspec_linux/system_info.py +0 -0
- {runspec_linux-0.4.0 → runspec_linux-0.4.2}/tests/__init__.py +0 -0
- {runspec_linux-0.4.0 → runspec_linux-0.4.2}/tests/test_nc_send.py +0 -0
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# runspec-linux Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.2] — 2026-06-21
|
|
4
|
+
|
|
5
|
+
Add 3 runnables for managing Python virtualenvs on a fleet host — thin wrappers
|
|
6
|
+
over the new `runspec-linux-core` 0.3.2 helpers (dependency floor bumped to
|
|
7
|
+
`>=0.3.2`). All three are `autonomy = "confirm"`.
|
|
8
|
+
|
|
9
|
+
- **`create-venv`** — create a venv (always `--symlinks`) from a *specific*
|
|
10
|
+
interpreter (`--python` is a browsable `path` arg → pick the remote binary, not
|
|
11
|
+
the system default) at a free `--dest`. Creates a shared `<dest>/logs` dir mode
|
|
12
|
+
`2775` and can `--group` (chgrp) the venv to a shared group. Flags:
|
|
13
|
+
`--system-site-packages`, `--upgrade-deps`, `--clear`, `--prompt`.
|
|
14
|
+
- **`install-into-venv`** — `pip install` named `--package`(s) and/or a local
|
|
15
|
+
`--project` (a `pyproject.toml` directory or wheel/sdist; `--editable` for
|
|
16
|
+
`-e`) into a venv. Flags: `--upgrade`/`-U`, `--no-cache-dir`,
|
|
17
|
+
`--force-reinstall`.
|
|
18
|
+
- **`configure-pip`** — write a `pip.conf` (`--index-url` + `--extra-index-url`,
|
|
19
|
+
`--trusted-host`, `--cert`, `--timeout`, `--retries`, `--proxy`,
|
|
20
|
+
`--require-virtualenv`) at `--scope user` (`~/.config/pip/pip.conf`) or
|
|
21
|
+
`--scope venv` (`<venv>/pip.conf`) for private-PyPI access.
|
|
22
|
+
|
|
23
|
+
Unlike the package/power runnables, these declare **no `run_as`** in
|
|
24
|
+
`runspec.toml`. The service account that owns a shared venv is chosen per run, so
|
|
25
|
+
each takes a **`--run-as`** argument (and a `--become-method` of `sudo` or `su`)
|
|
26
|
+
and escalates internally — an empty `--run-as` runs as the SSH login user
|
|
27
|
+
(personal venv).
|
|
28
|
+
|
|
29
|
+
## [0.4.1] — 2026-06-20
|
|
30
|
+
|
|
31
|
+
Give `list-upgrades` `run_as = "root"`. It refreshes the package index
|
|
32
|
+
(`apt-get update` / `dnf check-update` / …) by default, which needs root — but it
|
|
33
|
+
had no `run_as`, so over the console it ran as the SSH login user and failed with
|
|
34
|
+
`Could not open lock file /var/lib/apt/lists/lock — Permission denied`. It now
|
|
35
|
+
escalates like the rest of the package family (works password-free with the
|
|
36
|
+
`enable-passwordless-sudo` drop-in). `--no-refresh` still skips the index refresh
|
|
37
|
+
and lists from the cache (now just faster, rather than a non-root workaround).
|
|
38
|
+
|
|
3
39
|
## [0.4.0] — 2026-06-20
|
|
4
40
|
|
|
5
41
|
Make the privileged package/power runnables escalate via runspec's `run_as`, and
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runspec-linux
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Linux system admin runnables for runspec
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
|
-
Requires-Dist: runspec-linux-core>=0.3.
|
|
6
|
+
Requires-Dist: runspec-linux-core>=0.3.2
|
|
7
7
|
Requires-Dist: runspec>=0.27.0
|
|
8
8
|
Provides-Extra: dev
|
|
9
9
|
Requires-Dist: mypy; extra == 'dev'
|
|
@@ -4,12 +4,12 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "runspec-linux"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.2"
|
|
8
8
|
requires-python = ">=3.10"
|
|
9
9
|
description = "Linux system admin runnables for runspec"
|
|
10
10
|
dependencies = [
|
|
11
11
|
"runspec>=0.27.0",
|
|
12
|
-
"runspec-linux-core>=0.3.
|
|
12
|
+
"runspec-linux-core>=0.3.2",
|
|
13
13
|
]
|
|
14
14
|
|
|
15
15
|
[project.scripts]
|
|
@@ -40,6 +40,9 @@ install-package = "runspec_linux.packages:main_install_package"
|
|
|
40
40
|
remove-package = "runspec_linux.packages:main_remove_package"
|
|
41
41
|
reboot-host = "runspec_linux.power:main_reboot_host"
|
|
42
42
|
enable-passwordless-sudo = "runspec_linux.sudoers:main_enable_passwordless_sudo"
|
|
43
|
+
create-venv = "runspec_linux.venvs:main_create_venv"
|
|
44
|
+
install-into-venv = "runspec_linux.venvs:main_install_into_venv"
|
|
45
|
+
configure-pip = "runspec_linux.venvs:main_configure_pip"
|
|
43
46
|
nc-command = "runspec_linux.nc_command:main"
|
|
44
47
|
|
|
45
48
|
[project.optional-dependencies]
|
|
@@ -12,11 +12,14 @@ from runspec_linux_core import (
|
|
|
12
12
|
check_memory,
|
|
13
13
|
check_port,
|
|
14
14
|
check_service,
|
|
15
|
+
configure_pip,
|
|
15
16
|
container_logs,
|
|
17
|
+
create_venv,
|
|
16
18
|
detect_manager,
|
|
17
19
|
disk_usage,
|
|
18
20
|
enable_passwordless_sudo,
|
|
19
21
|
find_large_files,
|
|
22
|
+
install_into_venv,
|
|
20
23
|
install_package,
|
|
21
24
|
journalctl,
|
|
22
25
|
last_logins,
|
|
@@ -44,11 +47,14 @@ __all__ = [
|
|
|
44
47
|
"check_memory",
|
|
45
48
|
"check_port",
|
|
46
49
|
"check_service",
|
|
50
|
+
"configure_pip",
|
|
47
51
|
"container_logs",
|
|
52
|
+
"create_venv",
|
|
48
53
|
"detect_manager",
|
|
49
54
|
"disk_usage",
|
|
50
55
|
"enable_passwordless_sudo",
|
|
51
56
|
"find_large_files",
|
|
57
|
+
"install_into_venv",
|
|
52
58
|
"install_package",
|
|
53
59
|
"journalctl",
|
|
54
60
|
"last_logins",
|
|
@@ -324,10 +324,11 @@ serve = ["local"]
|
|
|
324
324
|
description = "List packages with an available upgrade (refreshes the package index first)"
|
|
325
325
|
autonomy = "autonomous"
|
|
326
326
|
output = "json"
|
|
327
|
+
run_as = "root"
|
|
327
328
|
|
|
328
329
|
[list-upgrades.args.no-refresh]
|
|
329
330
|
type = "flag"
|
|
330
|
-
description = "
|
|
331
|
+
description = "Skip refreshing the package index — list from the cached index (faster)"
|
|
331
332
|
default = false
|
|
332
333
|
|
|
333
334
|
[upgrade-packages]
|
|
@@ -437,3 +438,190 @@ default = 0.3
|
|
|
437
438
|
type = "float"
|
|
438
439
|
description = "Per-chunk socket read timeout in seconds"
|
|
439
440
|
default = 0.1
|
|
441
|
+
|
|
442
|
+
# ── Python venvs ──────────────────────────────────────────────────────────────
|
|
443
|
+
|
|
444
|
+
# These deliberately declare NO `run_as` key. The service account that owns a
|
|
445
|
+
# shared venv is chosen per invocation, so each runnable takes a `run-as`
|
|
446
|
+
# argument and escalates internally (`sudo -n -u <user>`, or `su` via
|
|
447
|
+
# `become-method`). An empty `run-as` runs as the SSH login user (personal venv).
|
|
448
|
+
|
|
449
|
+
[create-venv]
|
|
450
|
+
serve = ["local"]
|
|
451
|
+
description = "Create a Python virtualenv (always --symlinks) from a specific interpreter, optionally as a service account, with a shared 2775 logs/ dir"
|
|
452
|
+
autonomy = "confirm"
|
|
453
|
+
output = "json"
|
|
454
|
+
|
|
455
|
+
[create-venv.args.python]
|
|
456
|
+
type = "path"
|
|
457
|
+
description = "Path to the Python interpreter to build the venv from (browse to a real binary — not the system default)"
|
|
458
|
+
|
|
459
|
+
[create-venv.args.dest]
|
|
460
|
+
type = "path"
|
|
461
|
+
short = "-d"
|
|
462
|
+
description = "Full path of the venv to create (name and location are free)"
|
|
463
|
+
|
|
464
|
+
[create-venv.args.run-as]
|
|
465
|
+
type = "str"
|
|
466
|
+
short = "-u"
|
|
467
|
+
description = "Service account to create the venv as (escalates via sudo/su); empty = the SSH login user"
|
|
468
|
+
required = false
|
|
469
|
+
|
|
470
|
+
[create-venv.args.become-method]
|
|
471
|
+
type = "choice"
|
|
472
|
+
options = ["sudo", "su"]
|
|
473
|
+
default = "sudo"
|
|
474
|
+
description = "How to escalate to run-as: sudo (passwordless, -n) or su"
|
|
475
|
+
|
|
476
|
+
[create-venv.args.group]
|
|
477
|
+
type = "str"
|
|
478
|
+
short = "-g"
|
|
479
|
+
description = "Optional shared group to chgrp the venv to (e.g. consoles), so every connected console can read/write"
|
|
480
|
+
required = false
|
|
481
|
+
|
|
482
|
+
[create-venv.args.system-site-packages]
|
|
483
|
+
type = "flag"
|
|
484
|
+
description = "Give the venv access to the system site-packages"
|
|
485
|
+
default = false
|
|
486
|
+
|
|
487
|
+
[create-venv.args.upgrade-deps]
|
|
488
|
+
type = "flag"
|
|
489
|
+
description = "Upgrade pip/setuptools to the latest in the new venv (--upgrade-deps)"
|
|
490
|
+
default = false
|
|
491
|
+
|
|
492
|
+
[create-venv.args.clear]
|
|
493
|
+
type = "flag"
|
|
494
|
+
description = "Delete the contents of the venv directory if it already exists (--clear)"
|
|
495
|
+
default = false
|
|
496
|
+
|
|
497
|
+
[create-venv.args.prompt]
|
|
498
|
+
type = "str"
|
|
499
|
+
short = "-p"
|
|
500
|
+
description = "Prompt prefix shown when the venv is activated (--prompt)"
|
|
501
|
+
required = false
|
|
502
|
+
|
|
503
|
+
[install-into-venv]
|
|
504
|
+
serve = ["local"]
|
|
505
|
+
description = "pip install named packages and/or a local project (pyproject dir or wheel/sdist) into an existing venv"
|
|
506
|
+
autonomy = "confirm"
|
|
507
|
+
output = "json"
|
|
508
|
+
|
|
509
|
+
[install-into-venv.args.venv]
|
|
510
|
+
type = "path"
|
|
511
|
+
short = "-v"
|
|
512
|
+
description = "Path to the venv to install into (its bundled python runs pip)"
|
|
513
|
+
|
|
514
|
+
[install-into-venv.args.package]
|
|
515
|
+
type = "str"
|
|
516
|
+
short = "-p"
|
|
517
|
+
description = "Package spec(s) to install (space-separated for several)"
|
|
518
|
+
required = false
|
|
519
|
+
|
|
520
|
+
[install-into-venv.args.project]
|
|
521
|
+
type = "path"
|
|
522
|
+
description = "Path to a local project to install — a directory with pyproject.toml, or a wheel/sdist"
|
|
523
|
+
required = false
|
|
524
|
+
|
|
525
|
+
[install-into-venv.args.editable]
|
|
526
|
+
type = "flag"
|
|
527
|
+
short = "-e"
|
|
528
|
+
description = "Install the project in editable mode (pip install -e); requires --project"
|
|
529
|
+
default = false
|
|
530
|
+
|
|
531
|
+
[install-into-venv.args.upgrade]
|
|
532
|
+
type = "flag"
|
|
533
|
+
short = "-U"
|
|
534
|
+
description = "Upgrade packages to the newest available version (pip install -U)"
|
|
535
|
+
default = false
|
|
536
|
+
|
|
537
|
+
[install-into-venv.args.no-cache-dir]
|
|
538
|
+
type = "flag"
|
|
539
|
+
description = "Disable the pip cache for this install (--no-cache-dir)"
|
|
540
|
+
default = false
|
|
541
|
+
|
|
542
|
+
[install-into-venv.args.force-reinstall]
|
|
543
|
+
type = "flag"
|
|
544
|
+
description = "Reinstall packages even if already up to date (--force-reinstall)"
|
|
545
|
+
default = false
|
|
546
|
+
|
|
547
|
+
[install-into-venv.args.run-as]
|
|
548
|
+
type = "str"
|
|
549
|
+
short = "-u"
|
|
550
|
+
description = "Service account that owns the venv (escalates via sudo/su); empty = the SSH login user"
|
|
551
|
+
required = false
|
|
552
|
+
|
|
553
|
+
[install-into-venv.args.become-method]
|
|
554
|
+
type = "choice"
|
|
555
|
+
options = ["sudo", "su"]
|
|
556
|
+
default = "sudo"
|
|
557
|
+
description = "How to escalate to run-as: sudo (passwordless, -n) or su"
|
|
558
|
+
|
|
559
|
+
[configure-pip]
|
|
560
|
+
serve = ["local"]
|
|
561
|
+
description = "Write a pip.conf (index-url and friends) at user or venv scope for private-PyPI access"
|
|
562
|
+
autonomy = "confirm"
|
|
563
|
+
output = "json"
|
|
564
|
+
|
|
565
|
+
[configure-pip.args.index-url]
|
|
566
|
+
type = "str"
|
|
567
|
+
short = "-i"
|
|
568
|
+
description = "Primary package index URL (index-url)"
|
|
569
|
+
|
|
570
|
+
[configure-pip.args.extra-index-url]
|
|
571
|
+
type = "str"
|
|
572
|
+
description = "Additional package index URL (extra-index-url)"
|
|
573
|
+
required = false
|
|
574
|
+
|
|
575
|
+
[configure-pip.args.trusted-host]
|
|
576
|
+
type = "str"
|
|
577
|
+
description = "Host to trust without TLS verification (trusted-host)"
|
|
578
|
+
required = false
|
|
579
|
+
|
|
580
|
+
[configure-pip.args.cert]
|
|
581
|
+
type = "str"
|
|
582
|
+
description = "Path to a CA bundle to verify the index's TLS certificate (cert)"
|
|
583
|
+
required = false
|
|
584
|
+
|
|
585
|
+
[configure-pip.args.timeout]
|
|
586
|
+
type = "int"
|
|
587
|
+
description = "Socket timeout in seconds (timeout)"
|
|
588
|
+
required = false
|
|
589
|
+
|
|
590
|
+
[configure-pip.args.retries]
|
|
591
|
+
type = "int"
|
|
592
|
+
description = "Number of retries for a failed connection (retries)"
|
|
593
|
+
required = false
|
|
594
|
+
|
|
595
|
+
[configure-pip.args.proxy]
|
|
596
|
+
type = "str"
|
|
597
|
+
description = "Proxy URL in [user:passwd@]proxy.server:port form (proxy)"
|
|
598
|
+
required = false
|
|
599
|
+
|
|
600
|
+
[configure-pip.args.require-virtualenv]
|
|
601
|
+
type = "flag"
|
|
602
|
+
description = "Refuse to install outside a virtualenv and silence the pip-version check (require-virtualenv + disable-pip-version-check)"
|
|
603
|
+
default = false
|
|
604
|
+
|
|
605
|
+
[configure-pip.args.scope]
|
|
606
|
+
type = "choice"
|
|
607
|
+
options = ["user", "venv"]
|
|
608
|
+
default = "user"
|
|
609
|
+
short = "-s"
|
|
610
|
+
description = "Where to write pip.conf: user (~/.config/pip/pip.conf) or venv (<venv>/pip.conf)"
|
|
611
|
+
|
|
612
|
+
[configure-pip.args.venv]
|
|
613
|
+
type = "path"
|
|
614
|
+
description = "Venv path for --scope venv (the file is written to <venv>/pip.conf)"
|
|
615
|
+
required = false
|
|
616
|
+
|
|
617
|
+
[configure-pip.args.run-as]
|
|
618
|
+
type = "str"
|
|
619
|
+
short = "-u"
|
|
620
|
+
description = "User who owns the target file (escalates via sudo/su); empty = the SSH login user"
|
|
621
|
+
required = false
|
|
622
|
+
|
|
623
|
+
[configure-pip.args.become-method]
|
|
624
|
+
type = "choice"
|
|
625
|
+
options = ["sudo", "su"]
|
|
626
|
+
default = "sudo"
|
|
627
|
+
description = "How to escalate to run-as: sudo (passwordless, -n) or su"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import runspec as rs
|
|
5
|
+
from runspec_linux_core import configure_pip, create_venv, install_into_venv
|
|
6
|
+
from runspec_linux_core.errors import CommandError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main_create_venv() -> None:
|
|
10
|
+
spec = rs.parse("create-venv")
|
|
11
|
+
try:
|
|
12
|
+
result = create_venv(
|
|
13
|
+
str(spec.python.value),
|
|
14
|
+
str(spec.dest.value),
|
|
15
|
+
run_as=spec.run_as.value or None,
|
|
16
|
+
become_method=spec.become_method.value,
|
|
17
|
+
group=spec.group.value or None,
|
|
18
|
+
system_site_packages=spec.system_site_packages.value,
|
|
19
|
+
upgrade_deps=spec.upgrade_deps.value,
|
|
20
|
+
clear=spec.clear.value,
|
|
21
|
+
prompt=spec.prompt.value or None,
|
|
22
|
+
)
|
|
23
|
+
print(json.dumps(result))
|
|
24
|
+
except CommandError as e:
|
|
25
|
+
print(json.dumps({"dest": str(spec.dest.value), "created": False, "error": str(e)}))
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
except Exception as e:
|
|
28
|
+
# Includes ValueError (non-executable interpreter / bad name).
|
|
29
|
+
print(json.dumps({"error": str(e)}))
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def main_install_into_venv() -> None:
|
|
34
|
+
spec = rs.parse("install-into-venv")
|
|
35
|
+
try:
|
|
36
|
+
result = install_into_venv(
|
|
37
|
+
str(spec.venv.value),
|
|
38
|
+
spec.package.value or "",
|
|
39
|
+
project=str(spec.project.value) if spec.project.value else None,
|
|
40
|
+
editable=spec.editable.value,
|
|
41
|
+
upgrade=spec.upgrade.value,
|
|
42
|
+
no_cache_dir=spec.no_cache_dir.value,
|
|
43
|
+
force_reinstall=spec.force_reinstall.value,
|
|
44
|
+
run_as=spec.run_as.value or None,
|
|
45
|
+
become_method=spec.become_method.value,
|
|
46
|
+
)
|
|
47
|
+
print(json.dumps(result))
|
|
48
|
+
except CommandError as e:
|
|
49
|
+
print(json.dumps({"venv": str(spec.venv.value), "installed": False, "error": str(e)}))
|
|
50
|
+
sys.exit(1)
|
|
51
|
+
except Exception as e:
|
|
52
|
+
# Includes ValueError (bad package name / missing venv / nothing to install).
|
|
53
|
+
print(json.dumps({"error": str(e)}))
|
|
54
|
+
sys.exit(1)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def main_configure_pip() -> None:
|
|
58
|
+
spec = rs.parse("configure-pip")
|
|
59
|
+
try:
|
|
60
|
+
result = configure_pip(
|
|
61
|
+
spec.index_url.value,
|
|
62
|
+
extra_index_url=spec.extra_index_url.value or None,
|
|
63
|
+
trusted_host=spec.trusted_host.value or None,
|
|
64
|
+
cert=spec.cert.value or None,
|
|
65
|
+
timeout=spec.timeout.value,
|
|
66
|
+
retries=spec.retries.value,
|
|
67
|
+
proxy=spec.proxy.value or None,
|
|
68
|
+
require_virtualenv=spec.require_virtualenv.value,
|
|
69
|
+
scope=spec.scope.value,
|
|
70
|
+
venv=str(spec.venv.value) if spec.venv.value else None,
|
|
71
|
+
run_as=spec.run_as.value or None,
|
|
72
|
+
become_method=spec.become_method.value,
|
|
73
|
+
)
|
|
74
|
+
print(json.dumps(result))
|
|
75
|
+
except CommandError as e:
|
|
76
|
+
print(json.dumps({"configured": False, "error": str(e)}))
|
|
77
|
+
sys.exit(1)
|
|
78
|
+
except Exception as e:
|
|
79
|
+
# Includes ValueError (bad scope / missing venv path).
|
|
80
|
+
print(json.dumps({"error": str(e)}))
|
|
81
|
+
sys.exit(1)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|