runspec-linux 0.4.1__tar.gz → 0.4.3__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.

Potentially problematic release.


This version of runspec-linux might be problematic. Click here for more details.

@@ -1,5 +1,40 @@
1
1
  # runspec-linux Changelog
2
2
 
3
+ ## [0.4.3] — 2026-06-21
4
+
5
+ - **`install-into-venv --venv` and `configure-pip --venv` are now marked
6
+ `ui = "directory"`.** Both point at an existing venv *folder*, so the console's
7
+ path picker opens a folder picker ("Use this folder") instead of a file picker
8
+ — it was previously unclear the arg wanted the venv directory, not the
9
+ `bin/python` inside it. (`install-into-venv --project` stays a generic `path`
10
+ since it accepts either a project directory or a wheel/sdist file.)
11
+
12
+ ## [0.4.2] — 2026-06-21
13
+
14
+ Add 3 runnables for managing Python virtualenvs on a fleet host — thin wrappers
15
+ over the new `runspec-linux-core` 0.3.2 helpers (dependency floor bumped to
16
+ `>=0.3.2`). All three are `autonomy = "confirm"`.
17
+
18
+ - **`create-venv`** — create a venv (always `--symlinks`) from a *specific*
19
+ interpreter (`--python` is a browsable `path` arg → pick the remote binary, not
20
+ the system default) at a free `--dest`. Creates a shared `<dest>/logs` dir mode
21
+ `2775` and can `--group` (chgrp) the venv to a shared group. Flags:
22
+ `--system-site-packages`, `--upgrade-deps`, `--clear`, `--prompt`.
23
+ - **`install-into-venv`** — `pip install` named `--package`(s) and/or a local
24
+ `--project` (a `pyproject.toml` directory or wheel/sdist; `--editable` for
25
+ `-e`) into a venv. Flags: `--upgrade`/`-U`, `--no-cache-dir`,
26
+ `--force-reinstall`.
27
+ - **`configure-pip`** — write a `pip.conf` (`--index-url` + `--extra-index-url`,
28
+ `--trusted-host`, `--cert`, `--timeout`, `--retries`, `--proxy`,
29
+ `--require-virtualenv`) at `--scope user` (`~/.config/pip/pip.conf`) or
30
+ `--scope venv` (`<venv>/pip.conf`) for private-PyPI access.
31
+
32
+ Unlike the package/power runnables, these declare **no `run_as`** in
33
+ `runspec.toml`. The service account that owns a shared venv is chosen per run, so
34
+ each takes a **`--run-as`** argument (and a `--become-method` of `sudo` or `su`)
35
+ and escalates internally — an empty `--run-as` runs as the SSH login user
36
+ (personal venv).
37
+
3
38
  ## [0.4.1] — 2026-06-20
4
39
 
5
40
  Give `list-upgrades` `run_as = "root"`. It refreshes the package index
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: runspec-linux
3
- Version: 0.4.1
3
+ Version: 0.4.3
4
4
  Summary: Linux system admin runnables for runspec
5
5
  Requires-Python: >=3.10
6
- Requires-Dist: runspec-linux-core>=0.3.0
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.1"
7
+ version = "0.4.3"
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.0",
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",
@@ -438,3 +438,192 @@ default = 0.3
438
438
  type = "float"
439
439
  description = "Per-chunk socket read timeout in seconds"
440
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
+ ui = "directory"
512
+ short = "-v"
513
+ description = "Path to the venv to install into (its bundled python runs pip)"
514
+
515
+ [install-into-venv.args.package]
516
+ type = "str"
517
+ short = "-p"
518
+ description = "Package spec(s) to install (space-separated for several)"
519
+ required = false
520
+
521
+ [install-into-venv.args.project]
522
+ type = "path"
523
+ description = "Path to a local project to install — a directory with pyproject.toml, or a wheel/sdist"
524
+ required = false
525
+
526
+ [install-into-venv.args.editable]
527
+ type = "flag"
528
+ short = "-e"
529
+ description = "Install the project in editable mode (pip install -e); requires --project"
530
+ default = false
531
+
532
+ [install-into-venv.args.upgrade]
533
+ type = "flag"
534
+ short = "-U"
535
+ description = "Upgrade packages to the newest available version (pip install -U)"
536
+ default = false
537
+
538
+ [install-into-venv.args.no-cache-dir]
539
+ type = "flag"
540
+ description = "Disable the pip cache for this install (--no-cache-dir)"
541
+ default = false
542
+
543
+ [install-into-venv.args.force-reinstall]
544
+ type = "flag"
545
+ description = "Reinstall packages even if already up to date (--force-reinstall)"
546
+ default = false
547
+
548
+ [install-into-venv.args.run-as]
549
+ type = "str"
550
+ short = "-u"
551
+ description = "Service account that owns the venv (escalates via sudo/su); empty = the SSH login user"
552
+ required = false
553
+
554
+ [install-into-venv.args.become-method]
555
+ type = "choice"
556
+ options = ["sudo", "su"]
557
+ default = "sudo"
558
+ description = "How to escalate to run-as: sudo (passwordless, -n) or su"
559
+
560
+ [configure-pip]
561
+ serve = ["local"]
562
+ description = "Write a pip.conf (index-url and friends) at user or venv scope for private-PyPI access"
563
+ autonomy = "confirm"
564
+ output = "json"
565
+
566
+ [configure-pip.args.index-url]
567
+ type = "str"
568
+ short = "-i"
569
+ description = "Primary package index URL (index-url)"
570
+
571
+ [configure-pip.args.extra-index-url]
572
+ type = "str"
573
+ description = "Additional package index URL (extra-index-url)"
574
+ required = false
575
+
576
+ [configure-pip.args.trusted-host]
577
+ type = "str"
578
+ description = "Host to trust without TLS verification (trusted-host)"
579
+ required = false
580
+
581
+ [configure-pip.args.cert]
582
+ type = "str"
583
+ description = "Path to a CA bundle to verify the index's TLS certificate (cert)"
584
+ required = false
585
+
586
+ [configure-pip.args.timeout]
587
+ type = "int"
588
+ description = "Socket timeout in seconds (timeout)"
589
+ required = false
590
+
591
+ [configure-pip.args.retries]
592
+ type = "int"
593
+ description = "Number of retries for a failed connection (retries)"
594
+ required = false
595
+
596
+ [configure-pip.args.proxy]
597
+ type = "str"
598
+ description = "Proxy URL in [user:passwd@]proxy.server:port form (proxy)"
599
+ required = false
600
+
601
+ [configure-pip.args.require-virtualenv]
602
+ type = "flag"
603
+ description = "Refuse to install outside a virtualenv and silence the pip-version check (require-virtualenv + disable-pip-version-check)"
604
+ default = false
605
+
606
+ [configure-pip.args.scope]
607
+ type = "choice"
608
+ options = ["user", "venv"]
609
+ default = "user"
610
+ short = "-s"
611
+ description = "Where to write pip.conf: user (~/.config/pip/pip.conf) or venv (<venv>/pip.conf)"
612
+
613
+ [configure-pip.args.venv]
614
+ type = "path"
615
+ ui = "directory"
616
+ description = "Venv path for --scope venv (the file is written to <venv>/pip.conf)"
617
+ required = false
618
+
619
+ [configure-pip.args.run-as]
620
+ type = "str"
621
+ short = "-u"
622
+ description = "User who owns the target file (escalates via sudo/su); empty = the SSH login user"
623
+ required = false
624
+
625
+ [configure-pip.args.become-method]
626
+ type = "choice"
627
+ options = ["sudo", "su"]
628
+ default = "sudo"
629
+ 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