runspec-linux 0.17.0__tar.gz → 0.20.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.
Files changed (39) hide show
  1. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/CHANGELOG.md +73 -0
  2. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/PKG-INFO +22 -23
  3. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/README.md +20 -21
  4. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/pyproject.toml +2 -14
  5. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/runspec_linux/__init__.py +2 -19
  6. runspec_linux-0.20.0/runspec_linux/_escalation.py +42 -0
  7. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/runspec_linux/cron.py +4 -1
  8. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/runspec_linux/fs/cli.py +2 -0
  9. runspec_linux-0.20.0/runspec_linux/packages.py +27 -0
  10. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/runspec_linux/perf.py +1 -20
  11. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/runspec_linux/runspec.toml +20 -475
  12. runspec_linux-0.20.0/tests/test_escalation.py +56 -0
  13. runspec_linux-0.17.0/runspec_linux/_escalation.py +0 -20
  14. runspec_linux-0.17.0/runspec_linux/packages.py +0 -99
  15. runspec_linux-0.17.0/runspec_linux/power.py +0 -20
  16. runspec_linux-0.17.0/runspec_linux/sudoers.py +0 -23
  17. runspec_linux-0.17.0/runspec_linux/venvs.py +0 -100
  18. runspec_linux-0.17.0/tests/test_escalation.py +0 -34
  19. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/.gitignore +0 -0
  20. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/runspec_linux/commands.py +0 -0
  21. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/runspec_linux/containers.py +0 -0
  22. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/runspec_linux/files.py +0 -0
  23. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/runspec_linux/fs/__init__.py +0 -0
  24. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/runspec_linux/fs/_dispatch.py +0 -0
  25. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/runspec_linux/fs/_worker.py +0 -0
  26. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/runspec_linux/investigate.py +0 -0
  27. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/runspec_linux/logs.py +0 -0
  28. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/runspec_linux/nc_command.py +0 -0
  29. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/runspec_linux/network.py +0 -0
  30. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/runspec_linux/security.py +0 -0
  31. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/runspec_linux/services.py +0 -0
  32. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/runspec_linux/system_info.py +0 -0
  33. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/runspec_linux/webops.py +0 -0
  34. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/tests/__init__.py +0 -0
  35. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/tests/test_files.py +0 -0
  36. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/tests/test_fs.py +0 -0
  37. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/tests/test_inventory.py +0 -0
  38. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/tests/test_nc_send.py +0 -0
  39. {runspec_linux-0.17.0 → runspec_linux-0.20.0}/tests/test_webops.py +0 -0
@@ -1,5 +1,78 @@
1
1
  # runspec-linux Changelog
2
2
 
3
+ ## [0.20.0] — 2026-10-02
4
+
5
+ **Venv management moved to runspec-console.** Setting up a venv is a console
6
+ job now: runspec-console 0.312.0 provisions a runspec venv on a host from
7
+ **Settings → Jump Hosts → Provision new venv…** (interpreter, location,
8
+ run-as account, packages, logs inbox), and its **Marketplace** tab installs
9
+ into existing venvs. On the command line, plain `python -m venv`,
10
+ `pip install` and `runspec logs share --inbox` do the same job.
11
+
12
+ ### Removed
13
+
14
+ - `create-venv`, `install-into-venv`, `configure-pip` and `share-venv-logs`.
15
+ - The `runspec_linux` re-exports of `create_venv`, `install_into_venv` and
16
+ `configure_pip`. Their helpers are also gone from `runspec-linux-core`
17
+ 0.14.0.
18
+
19
+ Upgrade `runspec-linux` and `runspec-linux-core` together: runspec-linux
20
+ 0.19.0 still imports the removed helpers, so it breaks against
21
+ runspec-linux-core 0.14.0.
22
+
23
+ ## [0.19.0] — 2026-09-30
24
+
25
+ **No root in runspec-linux.** Everything here now runs as an unprivileged
26
+ account: you, or the service account that owns a shared venv (runspec >= 0.68.0
27
+ runs a shared venv's tools as its owner). The tools that need root moved to the
28
+ new **`runspec-linux-root`** package, for a root-owned admin venv.
29
+
30
+ ### Removed (moved to `runspec-linux-root`)
31
+
32
+ - `list-upgrades`, `upgrade-packages`, `install-package`, `remove-package`,
33
+ `autoremove-packages`, `set-sysctl`, `reboot-host`, `enable-passwordless-sudo`.
34
+ Their helpers stay in `runspec-linux-core`. The `runspec_linux` re-exports of
35
+ `install_package`, `list_upgrades`, `reboot_host`, `remove_package`,
36
+ `upgrade_packages` and `enable_passwordless_sudo` are gone: import them from
37
+ `runspec_linux_core`.
38
+ - **`--sudo`** ("run as root") on `restart-service`, `tail-log`, `search-log`,
39
+ `journalctl`, `list-containers`, `container-logs`, `restart-container`,
40
+ `service-detail` and `open-ports`.
41
+
42
+ ### Changed
43
+
44
+ - **`--run-as root` is refused** on every tool that takes `--run-as` (the ones
45
+ above, the venv tools, `filesystem` and `cron`). It prints a JSON error naming
46
+ `runspec-linux-root` and exits 2. `--run-as` still selects any unprivileged
47
+ account.
48
+ - **`--run-as` naming the account you already are does nothing** (from
49
+ runspec-linux-core 0.13.0). A service account running its own venv's tools with
50
+ `--run-as <itself>` used to need a sudoers rule letting it sudo to itself.
51
+
52
+ ### Upgrading
53
+
54
+ Install `runspec-linux-root` into a separate root-owned venv (see its README).
55
+ If an existing venv has both packages, upgrade `runspec-linux` first: pip
56
+ removes the old copies of the moved scripts when it upgrades it.
57
+
58
+ ## [0.18.0] — 2026-09-29
59
+
60
+ ### Added
61
+
62
+ - **`create-venv` sets up the logs inbox** (`logs/inbox`, `01777`, owned by the
63
+ venv's owner). Operators' and root's runs of the venv's runnables then reach
64
+ its history with no group admin (runspec >= 0.66). `logs/` is owner-only
65
+ (`0755`) unless `--group` is given. `--no-inbox` keeps the pre-inbox `2775`
66
+ layout and writes a `.no-inbox` opt-out marker.
67
+ - **`share-venv-logs` sets up the logs inbox** on an existing venv (run it with
68
+ `--run-as <owner>`). `--group` still adds the `2775` setgid group share, and
69
+ `--no-inbox` does only the group share, as before.
70
+
71
+ ### Changed
72
+
73
+ - **`install-into-venv` keeps `logs/`'s mode** instead of forcing `2775`.
74
+ - Requires `runspec-linux-core >= 0.12.0`.
75
+
3
76
  ## [0.17.0] — 2026-09-26
4
77
 
5
78
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: runspec-linux
3
- Version: 0.17.0
3
+ Version: 0.20.0
4
4
  Summary: Linux system admin runnables for runspec
5
5
  Project-URL: Documentation, https://runspec.app/
6
6
  Project-URL: Source, https://github.com/jasonfinestone/runspec/tree/main/packages/python/runspec-linux
@@ -9,7 +9,7 @@ Project-URL: Issues, https://github.com/jasonfinestone/runspec/issues
9
9
  Keywords: linux,ops,runnable,runspec,ssh,sysadmin,venv
10
10
  Requires-Python: >=3.10
11
11
  Requires-Dist: runspec-fs-core>=0.5.0
12
- Requires-Dist: runspec-linux-core>=0.11.0
12
+ Requires-Dist: runspec-linux-core>=0.13.0
13
13
  Requires-Dist: runspec-webops-core>=0.3.0
14
14
  Requires-Dist: runspec>=0.50.1
15
15
  Provides-Extra: dev
@@ -20,17 +20,22 @@ Description-Content-Type: text/markdown
20
20
 
21
21
  # runspec-linux
22
22
 
23
- Linux system-administration runnables (plus Python-venv management, crontab,
23
+ Linux system-administration runnables (plus crontab,
24
24
  filesystem, and the cross-platform web/TLS/API probes) for
25
25
  [runspec](https://pypi.org/project/runspec/). `pip install` it into a venv and
26
26
  the runnables are discoverable by `runspec local`, `runspec serve` (MCP), and
27
27
  runspec-console.
28
28
 
29
29
  All runnables emit JSON on stdout. Read-only runnables are `autonomy =
30
- "autonomous"`; state-changing ones are `autonomy = "confirm"`. The privileged
31
- runnables declare `run_as = "root"`, so runspec-console escalates them via
32
- `sudo` — run `enable-passwordless-sudo` once to install a `visudo`-validated
33
- `/etc/sudoers.d` drop-in so they run without a password prompt.
30
+ "autonomous"`; state-changing ones are `autonomy = "confirm"`.
31
+
32
+ **Nothing here runs as root.** A tool runs as whoever owns its venv (runspec
33
+ >= 0.68.0): you in a personal venv, or the service account in a shared one.
34
+ `--run-as` picks another unprivileged account and refuses `root`. The tools that
35
+ need root (package upgrades/installs, `reboot-host`, `set-sysctl`,
36
+ `enable-passwordless-sudo`) are in
37
+ [`runspec-linux-root`](https://pypi.org/project/runspec-linux-root/), for a
38
+ root-owned admin venv.
34
39
 
35
40
  ## Install
36
41
 
@@ -47,41 +52,35 @@ pip install runspec-linux
47
52
  | Services | `list-services`, `check-service`, `restart-service`, `service-detail`, `failed-units` |
48
53
  | Logs | `tail-log`, `search-log`, `journalctl` |
49
54
  | Network | `network-interfaces`, `ping-host`, `check-port`, `show-connections`, `trace-route`, `route-table`, `socket-stats`, `dns-config`, `open-ports` |
50
- | Kernel params | `get-sysctl`, `set-sysctl` |
55
+ | Kernel params | `get-sysctl` |
51
56
  | Files | `find-large-files`, `backup-files`, `owning-package` |
52
57
  | Sessions | `last-logins`, `who` |
53
58
  | Containers | `list-containers`, `container-logs`, `restart-container` |
54
- | Packages | `list-packages`, `list-upgrades`, `upgrade-packages`, `install-package`, `remove-package`, `autoremove-packages`, `list-autoremovable` |
55
- | Power | `reboot-host` |
56
- | Sudo bootstrap | `enable-passwordless-sudo` |
57
- | Python venvs | `create-venv`, `install-into-venv`, `configure-pip` |
59
+ | Packages (read-only) | `list-packages`, `list-autoremovable` |
58
60
  | TCP | `nc-command` |
59
61
  | Commands | `which` |
60
62
  | Cron | `cron` (multi-verb: `list`/`add`/`remove`/`clear`) |
61
63
  | Filesystem | `filesystem` (multi-verb, backed by `runspec-fs-core`) |
62
64
  | Web / TLS / API | `cert-check`, `cert-chain`, `tls-info`, `http-check`, `http-headers`, `redirect-trace`, `dns-lookup`, `api-spec` |
63
65
 
64
- The package-management runnables auto-detect apt / dnf / yum / zypper / pacman.
66
+ The package runnables auto-detect apt / dnf / yum / zypper / pacman.
65
67
  The web/TLS/API probes are pure HTTP (shared with `runspec-windows` via
66
68
  `runspec-webops-core`) and run anywhere.
67
69
 
68
70
  ```
69
71
  system-info
70
- list-upgrades
72
+ list-packages --filter nginx
71
73
  check-port --host db-01 --port 5432
72
74
  cert-check --url https://internal.example.com
73
75
  ```
74
76
 
75
77
  ### Python venvs
76
78
 
77
- `create-venv`, `install-into-venv` and `configure-pip` take a **per-invocation**
78
- `--run-as <user>` (+ `--become-method sudo|su`) rather than a declarative
79
- `run_as`, so the service account that owns a shared venv is chosen per call:
80
-
81
- ```
82
- create-venv --python /usr/bin/python3.12 --dest /opt/venvs/ops --run-as svc-ops --group ops
83
- install-into-venv --venv /opt/venvs/ops --package "runspec-linux==0.13.2" --run-as svc-ops
84
- ```
79
+ The venv runnables (`create-venv`, `install-into-venv`, `configure-pip`,
80
+ `share-venv-logs`) were removed in 0.20.0. Provision a venv on a host from
81
+ runspec-console (**Settings → Jump Hosts → Provision new venv…**), install
82
+ packages from its **Marketplace** tab, or on the command line use
83
+ `python -m venv`, `pip install` and `runspec logs share --inbox`.
85
84
 
86
85
  ## Public Python API
87
86
 
@@ -91,7 +90,7 @@ runnables). A corporate wrapper package that only wants a helper should depend
91
90
  on the core alone:
92
91
 
93
92
  ```python
94
- from runspec_linux_core import nc_send, system_info, install_into_venv
93
+ from runspec_linux_core import nc_send, system_info, which
95
94
  ```
96
95
 
97
96
  The helpers are also re-exported from `runspec_linux` for back-compat. Core
@@ -1,16 +1,21 @@
1
1
  # runspec-linux
2
2
 
3
- Linux system-administration runnables (plus Python-venv management, crontab,
3
+ Linux system-administration runnables (plus crontab,
4
4
  filesystem, and the cross-platform web/TLS/API probes) for
5
5
  [runspec](https://pypi.org/project/runspec/). `pip install` it into a venv and
6
6
  the runnables are discoverable by `runspec local`, `runspec serve` (MCP), and
7
7
  runspec-console.
8
8
 
9
9
  All runnables emit JSON on stdout. Read-only runnables are `autonomy =
10
- "autonomous"`; state-changing ones are `autonomy = "confirm"`. The privileged
11
- runnables declare `run_as = "root"`, so runspec-console escalates them via
12
- `sudo` — run `enable-passwordless-sudo` once to install a `visudo`-validated
13
- `/etc/sudoers.d` drop-in so they run without a password prompt.
10
+ "autonomous"`; state-changing ones are `autonomy = "confirm"`.
11
+
12
+ **Nothing here runs as root.** A tool runs as whoever owns its venv (runspec
13
+ >= 0.68.0): you in a personal venv, or the service account in a shared one.
14
+ `--run-as` picks another unprivileged account and refuses `root`. The tools that
15
+ need root (package upgrades/installs, `reboot-host`, `set-sysctl`,
16
+ `enable-passwordless-sudo`) are in
17
+ [`runspec-linux-root`](https://pypi.org/project/runspec-linux-root/), for a
18
+ root-owned admin venv.
14
19
 
15
20
  ## Install
16
21
 
@@ -27,41 +32,35 @@ pip install runspec-linux
27
32
  | Services | `list-services`, `check-service`, `restart-service`, `service-detail`, `failed-units` |
28
33
  | Logs | `tail-log`, `search-log`, `journalctl` |
29
34
  | Network | `network-interfaces`, `ping-host`, `check-port`, `show-connections`, `trace-route`, `route-table`, `socket-stats`, `dns-config`, `open-ports` |
30
- | Kernel params | `get-sysctl`, `set-sysctl` |
35
+ | Kernel params | `get-sysctl` |
31
36
  | Files | `find-large-files`, `backup-files`, `owning-package` |
32
37
  | Sessions | `last-logins`, `who` |
33
38
  | Containers | `list-containers`, `container-logs`, `restart-container` |
34
- | Packages | `list-packages`, `list-upgrades`, `upgrade-packages`, `install-package`, `remove-package`, `autoremove-packages`, `list-autoremovable` |
35
- | Power | `reboot-host` |
36
- | Sudo bootstrap | `enable-passwordless-sudo` |
37
- | Python venvs | `create-venv`, `install-into-venv`, `configure-pip` |
39
+ | Packages (read-only) | `list-packages`, `list-autoremovable` |
38
40
  | TCP | `nc-command` |
39
41
  | Commands | `which` |
40
42
  | Cron | `cron` (multi-verb: `list`/`add`/`remove`/`clear`) |
41
43
  | Filesystem | `filesystem` (multi-verb, backed by `runspec-fs-core`) |
42
44
  | Web / TLS / API | `cert-check`, `cert-chain`, `tls-info`, `http-check`, `http-headers`, `redirect-trace`, `dns-lookup`, `api-spec` |
43
45
 
44
- The package-management runnables auto-detect apt / dnf / yum / zypper / pacman.
46
+ The package runnables auto-detect apt / dnf / yum / zypper / pacman.
45
47
  The web/TLS/API probes are pure HTTP (shared with `runspec-windows` via
46
48
  `runspec-webops-core`) and run anywhere.
47
49
 
48
50
  ```
49
51
  system-info
50
- list-upgrades
52
+ list-packages --filter nginx
51
53
  check-port --host db-01 --port 5432
52
54
  cert-check --url https://internal.example.com
53
55
  ```
54
56
 
55
57
  ### Python venvs
56
58
 
57
- `create-venv`, `install-into-venv` and `configure-pip` take a **per-invocation**
58
- `--run-as <user>` (+ `--become-method sudo|su`) rather than a declarative
59
- `run_as`, so the service account that owns a shared venv is chosen per call:
60
-
61
- ```
62
- create-venv --python /usr/bin/python3.12 --dest /opt/venvs/ops --run-as svc-ops --group ops
63
- install-into-venv --venv /opt/venvs/ops --package "runspec-linux==0.13.2" --run-as svc-ops
64
- ```
59
+ The venv runnables (`create-venv`, `install-into-venv`, `configure-pip`,
60
+ `share-venv-logs`) were removed in 0.20.0. Provision a venv on a host from
61
+ runspec-console (**Settings → Jump Hosts → Provision new venv…**), install
62
+ packages from its **Marketplace** tab, or on the command line use
63
+ `python -m venv`, `pip install` and `runspec logs share --inbox`.
65
64
 
66
65
  ## Public Python API
67
66
 
@@ -71,7 +70,7 @@ runnables). A corporate wrapper package that only wants a helper should depend
71
70
  on the core alone:
72
71
 
73
72
  ```python
74
- from runspec_linux_core import nc_send, system_info, install_into_venv
73
+ from runspec_linux_core import nc_send, system_info, which
75
74
  ```
76
75
 
77
76
  The helpers are also re-exported from `runspec_linux` for back-compat. Core
@@ -4,14 +4,14 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "runspec-linux"
7
- version = "0.17.0"
7
+ version = "0.20.0"
8
8
  requires-python = ">=3.10"
9
9
  description = "Linux system admin runnables for runspec"
10
10
  readme = "README.md"
11
11
  keywords = ["runspec", "runnable", "linux", "sysadmin", "ops", "ssh", "venv"]
12
12
  dependencies = [
13
13
  "runspec>=0.50.1",
14
- "runspec-linux-core>=0.11.0",
14
+ "runspec-linux-core>=0.13.0",
15
15
  "runspec-webops-core>=0.3.0",
16
16
  "runspec-fs-core>=0.5.0",
17
17
  ]
@@ -43,7 +43,6 @@ socket-stats = "runspec_linux.network:main_socket_stats"
43
43
  dns-config = "runspec_linux.network:main_dns_config"
44
44
  network-interfaces = "runspec_linux.network:main_network_interfaces"
45
45
  get-sysctl = "runspec_linux.perf:main_get_sysctl"
46
- set-sysctl = "runspec_linux.perf:main_set_sysctl"
47
46
  cpu-info = "runspec_linux.perf:main_cpu_info"
48
47
  pressure = "runspec_linux.perf:main_pressure"
49
48
  service-detail = "runspec_linux.investigate:main_service_detail"
@@ -58,18 +57,7 @@ list-containers = "runspec_linux.containers:main_list_containers"
58
57
  container-logs = "runspec_linux.containers:main_container_logs"
59
58
  restart-container = "runspec_linux.containers:main_restart_container"
60
59
  list-packages = "runspec_linux.packages:main_list_packages"
61
- list-upgrades = "runspec_linux.packages:main_list_upgrades"
62
- upgrade-packages = "runspec_linux.packages:main_upgrade_packages"
63
- install-package = "runspec_linux.packages:main_install_package"
64
- remove-package = "runspec_linux.packages:main_remove_package"
65
- autoremove-packages = "runspec_linux.packages:main_autoremove_packages"
66
60
  list-autoremovable = "runspec_linux.packages:main_list_autoremovable"
67
- reboot-host = "runspec_linux.power:main_reboot_host"
68
- enable-passwordless-sudo = "runspec_linux.sudoers:main_enable_passwordless_sudo"
69
- create-venv = "runspec_linux.venvs:main_create_venv"
70
- install-into-venv = "runspec_linux.venvs:main_install_into_venv"
71
- configure-pip = "runspec_linux.venvs:main_configure_pip"
72
- share-venv-logs = "runspec_linux.venvs:main_share_venv_logs"
73
61
  nc-command = "runspec_linux.nc_command:main"
74
62
  which = "runspec_linux.commands:main_which"
75
63
  # Cron / crontab management (backed by runspec-linux-core)
@@ -4,7 +4,8 @@ The pure logic lives in the dependency-free ``runspec-linux-core`` package; the
4
4
  modules here wrap each helper in a runspec runnable. The helper functions are
5
5
  re-exported for convenience and back-compat (e.g. ``from runspec_linux import
6
6
  nc_send``). To import the helpers *without* surfacing any runnables in a venv,
7
- depend on ``runspec-linux-core`` directly instead.
7
+ depend on ``runspec-linux-core`` directly instead. Tools that need root live in
8
+ ``runspec-linux-root``; their helpers are in ``runspec-linux-core``.
8
9
  """
9
10
 
10
11
  from runspec_linux_core import (
@@ -14,15 +15,10 @@ from runspec_linux_core import (
14
15
  check_port,
15
16
  check_service,
16
17
  clear_cron,
17
- configure_pip,
18
18
  container_logs,
19
- create_venv,
20
19
  detect_manager,
21
20
  disk_usage,
22
- enable_passwordless_sudo,
23
21
  find_large_files,
24
- install_into_venv,
25
- install_package,
26
22
  journalctl,
27
23
  last_logins,
28
24
  list_containers,
@@ -30,19 +26,15 @@ from runspec_linux_core import (
30
26
  list_packages,
31
27
  list_processes,
32
28
  list_services,
33
- list_upgrades,
34
29
  nc_send,
35
30
  ping_host,
36
- reboot_host,
37
31
  remove_cron,
38
- remove_package,
39
32
  restart_container,
40
33
  restart_service,
41
34
  search_log,
42
35
  show_connections,
43
36
  system_info,
44
37
  tail_log,
45
- upgrade_packages,
46
38
  which,
47
39
  who,
48
40
  )
@@ -54,15 +46,10 @@ __all__ = [
54
46
  "check_port",
55
47
  "check_service",
56
48
  "clear_cron",
57
- "configure_pip",
58
49
  "container_logs",
59
- "create_venv",
60
50
  "detect_manager",
61
51
  "disk_usage",
62
- "enable_passwordless_sudo",
63
52
  "find_large_files",
64
- "install_into_venv",
65
- "install_package",
66
53
  "journalctl",
67
54
  "last_logins",
68
55
  "list_containers",
@@ -70,19 +57,15 @@ __all__ = [
70
57
  "list_packages",
71
58
  "list_processes",
72
59
  "list_services",
73
- "list_upgrades",
74
60
  "nc_send",
75
61
  "ping_host",
76
- "reboot_host",
77
62
  "remove_cron",
78
- "remove_package",
79
63
  "restart_container",
80
64
  "restart_service",
81
65
  "search_log",
82
66
  "show_connections",
83
67
  "system_info",
84
68
  "tail_log",
85
- "upgrade_packages",
86
69
  "which",
87
70
  "who",
88
71
  ]
@@ -0,0 +1,42 @@
1
+ """Shared wrapper helper for the ``--run-as`` / ``--become-method`` escalation args.
2
+
3
+ runspec-linux never escalates to root: tools that need root live in the separate
4
+ ``runspec-linux-root`` package, installed into a root-owned venv. ``--run-as``
5
+ picks an unprivileged account (typically the service account that owns a shared
6
+ venv); a root target is refused before anything runs.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import sys
13
+ from typing import Any
14
+
15
+ ROOT_REFUSAL = "runspec-linux does not run anything as root — root tools live in runspec-linux-root"
16
+
17
+
18
+ def is_root(name: str) -> bool:
19
+ """True when ``name`` names uid 0 (``root``, ``0`` or any alias)."""
20
+ if name in ("root", "0"):
21
+ return True
22
+ try:
23
+ import pwd
24
+
25
+ return pwd.getpwnam(name).pw_uid == 0
26
+ except (ImportError, KeyError):
27
+ return False
28
+
29
+
30
+ def refuse_root(run_as: str | None) -> None:
31
+ """Print a JSON error and exit 2 when ``run_as`` targets root."""
32
+ if run_as and is_root(run_as):
33
+ print(json.dumps({"error": f"--run-as {run_as}: {ROOT_REFUSAL}"}))
34
+ sys.exit(2)
35
+
36
+
37
+ def resolve_escalation(spec: Any) -> tuple[str | None, str]:
38
+ """Return ``(run_as, become_method)`` from a spec carrying the escalation args
39
+ (``None`` = no escalation, runs as the caller). Exits on a root target."""
40
+ run_as = spec.run_as.value or None
41
+ refuse_root(run_as)
42
+ return run_as, spec.become_method.value
@@ -7,7 +7,7 @@ merged ``[cron]`` tables in the pack's ``runspec.toml``); we unpack each
7
7
  helper, which does the crontab read-modify-write.
8
8
 
9
9
  **Per-invocation ``--run-as``** (a global arg inherited by every verb, like the
10
- venv runnables): the operator/agent picks whose crontab to manage per call. The
10
+ container runnables): the operator/agent picks whose crontab to manage per call. The
11
11
  ``crontab`` command is escalated via ``sudo -n -u <user>`` / ``su`` inside the
12
12
  core helper, so "run as user *bob*" means "manage *bob*'s crontab". With no
13
13
  ``--run-as`` the caller's own crontab is used.
@@ -23,6 +23,8 @@ import runspec as rs
23
23
  from runspec_linux_core.cron import add_cron, clear_cron, list_cron, remove_cron
24
24
  from runspec_linux_core.errors import CommandError, ToolNotFoundError
25
25
 
26
+ from runspec_linux._escalation import refuse_root
27
+
26
28
 
27
29
  def _emit(payload: dict[str, Any]) -> None:
28
30
  print(json.dumps(payload, default=str))
@@ -31,6 +33,7 @@ def _emit(payload: dict[str, Any]) -> None:
31
33
  def _run(spec: rs.RunSpec) -> dict[str, Any]:
32
34
  command = spec.runspec_command
33
35
  run_as = spec.run_as.value or None
36
+ refuse_root(run_as)
34
37
  become_method = spec.become_method.value
35
38
 
36
39
  if command == "list":
@@ -26,6 +26,7 @@ from typing import Any
26
26
  import runspec as rs
27
27
  from runspec_fs_core import BecomeError, FsCoreError, build_become, current_user, escalation_supported
28
28
 
29
+ from runspec_linux._escalation import refuse_root
29
30
  from runspec_linux.fs._dispatch import run_verb
30
31
 
31
32
 
@@ -115,6 +116,7 @@ def _run_as_worker(run_as: str, method: str, verb: str, kwargs: dict[str, Any])
115
116
  def main() -> None:
116
117
  spec = rs.parse("filesystem")
117
118
  run_as = spec.run_as.value
119
+ refuse_root(run_as or None)
118
120
  method = spec.become_method.value
119
121
  try:
120
122
  kwargs = _verb_kwargs(spec)
@@ -0,0 +1,27 @@
1
+ import json
2
+ import sys
3
+
4
+ import runspec as rs
5
+ from runspec_linux_core import list_autoremovable, list_packages
6
+
7
+
8
+ def main_list_packages() -> None:
9
+ spec = rs.parse("list-packages")
10
+ name_filter = spec.filter.value
11
+ limit = spec.limit.value
12
+ try:
13
+ print(json.dumps(list_packages(name_filter, limit, reason=spec.reason.value)))
14
+ except Exception as e:
15
+ # Includes ToolNotFoundError (no package manager) and CommandError.
16
+ print(json.dumps({"error": str(e)}))
17
+ sys.exit(1)
18
+
19
+
20
+ def main_list_autoremovable() -> None:
21
+ spec = rs.parse("list-autoremovable")
22
+ try:
23
+ print(json.dumps(list_autoremovable(purge=spec.purge.value)))
24
+ except Exception as e:
25
+ # Includes CommandError (yum, failed listing) and ToolNotFoundError.
26
+ print(json.dumps({"error": str(e)}))
27
+ sys.exit(1)
@@ -4,10 +4,7 @@ import json
4
4
  import sys
5
5
 
6
6
  import runspec as rs
7
- from runspec_linux_core import cpu_info, get_sysctl, pressure, set_sysctl
8
- from runspec_linux_core.errors import CommandError
9
-
10
- from runspec_linux._escalation import resolve_escalation
7
+ from runspec_linux_core import cpu_info, get_sysctl, pressure
11
8
 
12
9
 
13
10
  def main_get_sysctl() -> None:
@@ -20,22 +17,6 @@ def main_get_sysctl() -> None:
20
17
  sys.exit(1)
21
18
 
22
19
 
23
- def main_set_sysctl() -> None:
24
- spec = rs.parse("set-sysctl")
25
- key = spec.key.value
26
- value = spec.value.value
27
- persist = spec.persist.value
28
- run_as, method = resolve_escalation(spec)
29
- try:
30
- print(json.dumps(set_sysctl(key, value, persist=persist, run_as=run_as, become_method=method)))
31
- except CommandError as e:
32
- print(json.dumps({"key": key, "applied": False, "error": str(e)}))
33
- sys.exit(1)
34
- except Exception as e:
35
- print(json.dumps({"error": str(e)}))
36
- sys.exit(1)
37
-
38
-
39
20
  def main_cpu_info() -> None:
40
21
  rs.parse("cpu-info")
41
22
  try: