runspec-linux 0.3.0__tar.gz → 0.4.1__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.3.0 → runspec_linux-0.4.1}/CHANGELOG.md +34 -0
- {runspec_linux-0.3.0 → runspec_linux-0.4.1}/PKG-INFO +2 -2
- {runspec_linux-0.3.0 → runspec_linux-0.4.1}/pyproject.toml +3 -2
- {runspec_linux-0.3.0 → runspec_linux-0.4.1}/runspec_linux/__init__.py +2 -0
- {runspec_linux-0.3.0 → runspec_linux-0.4.1}/runspec_linux/runspec.toml +34 -1
- runspec_linux-0.4.1/runspec_linux/sudoers.py +23 -0
- {runspec_linux-0.3.0 → runspec_linux-0.4.1}/.gitignore +0 -0
- {runspec_linux-0.3.0 → runspec_linux-0.4.1}/runspec_linux/containers.py +0 -0
- {runspec_linux-0.3.0 → runspec_linux-0.4.1}/runspec_linux/files.py +0 -0
- {runspec_linux-0.3.0 → runspec_linux-0.4.1}/runspec_linux/logs.py +0 -0
- {runspec_linux-0.3.0 → runspec_linux-0.4.1}/runspec_linux/nc_command.py +0 -0
- {runspec_linux-0.3.0 → runspec_linux-0.4.1}/runspec_linux/network.py +0 -0
- {runspec_linux-0.3.0 → runspec_linux-0.4.1}/runspec_linux/packages.py +0 -0
- {runspec_linux-0.3.0 → runspec_linux-0.4.1}/runspec_linux/power.py +0 -0
- {runspec_linux-0.3.0 → runspec_linux-0.4.1}/runspec_linux/security.py +0 -0
- {runspec_linux-0.3.0 → runspec_linux-0.4.1}/runspec_linux/services.py +0 -0
- {runspec_linux-0.3.0 → runspec_linux-0.4.1}/runspec_linux/system_info.py +0 -0
- {runspec_linux-0.3.0 → runspec_linux-0.4.1}/tests/__init__.py +0 -0
- {runspec_linux-0.3.0 → runspec_linux-0.4.1}/tests/test_nc_send.py +0 -0
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# runspec-linux Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.1] — 2026-06-20
|
|
4
|
+
|
|
5
|
+
Give `list-upgrades` `run_as = "root"`. It refreshes the package index
|
|
6
|
+
(`apt-get update` / `dnf check-update` / …) by default, which needs root — but it
|
|
7
|
+
had no `run_as`, so over the console it ran as the SSH login user and failed with
|
|
8
|
+
`Could not open lock file /var/lib/apt/lists/lock — Permission denied`. It now
|
|
9
|
+
escalates like the rest of the package family (works password-free with the
|
|
10
|
+
`enable-passwordless-sudo` drop-in). `--no-refresh` still skips the index refresh
|
|
11
|
+
and lists from the cache (now just faster, rather than a non-root workaround).
|
|
12
|
+
|
|
13
|
+
## [0.4.0] — 2026-06-20
|
|
14
|
+
|
|
15
|
+
Make the privileged package/power runnables escalate via runspec's `run_as`, and
|
|
16
|
+
add a one-time bootstrap runnable to enable it without a password. Bumps the
|
|
17
|
+
`runspec-linux-core` floor to `>=0.3.0`.
|
|
18
|
+
|
|
19
|
+
- **`run_as = "root"`** on `upgrade-packages`, `install-package`,
|
|
20
|
+
`remove-package`, and `reboot-host`. The console's SSH executor now wraps these
|
|
21
|
+
in `sudo -u root …` before they run, instead of failing with permission denied
|
|
22
|
+
when the SSH login user isn't root. (`list-packages` / `list-upgrades` stay
|
|
23
|
+
un-escalated — least privilege; `list-upgrades --no-refresh` works as any user.)
|
|
24
|
+
A site with a service account can override `run_as` in its own spec.
|
|
25
|
+
- **New `enable-passwordless-sudo`** (confirm) — the one-time setup that makes the
|
|
26
|
+
above work password-free. Takes a **`password`-typed** arg (delivered over the
|
|
27
|
+
console's secret channel and piped to `sudo -S` — never on argv/ps/logs),
|
|
28
|
+
optional `--user` (defaults to the login user), and `--scope`
|
|
29
|
+
(`scoped` = the runspec venv runnables only; `all` = `NOPASSWD: ALL`). Installs
|
|
30
|
+
a `visudo`-validated `/etc/sudoers.d/runspec`, rolling back on a bad result. It
|
|
31
|
+
declares no `run_as` (it bootstraps root itself).
|
|
32
|
+
|
|
33
|
+
Setup flow: run `enable-passwordless-sudo` once per host (pick `all` for
|
|
34
|
+
agent-driven fleet management; `scoped` is enough for manual/CLI runs), then
|
|
35
|
+
`upgrade-packages` and friends run without a password.
|
|
36
|
+
|
|
3
37
|
## [0.3.0] — 2026-06-20
|
|
4
38
|
|
|
5
39
|
Add 6 runnables for managing a fleet's packages and power state from
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runspec-linux
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Linux system admin runnables for runspec
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
|
-
Requires-Dist: runspec-linux-core>=0.
|
|
6
|
+
Requires-Dist: runspec-linux-core>=0.3.0
|
|
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.
|
|
7
|
+
version = "0.4.1"
|
|
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.
|
|
12
|
+
"runspec-linux-core>=0.3.0",
|
|
13
13
|
]
|
|
14
14
|
|
|
15
15
|
[project.scripts]
|
|
@@ -39,6 +39,7 @@ upgrade-packages = "runspec_linux.packages:main_upgrade_packages"
|
|
|
39
39
|
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
|
+
enable-passwordless-sudo = "runspec_linux.sudoers:main_enable_passwordless_sudo"
|
|
42
43
|
nc-command = "runspec_linux.nc_command:main"
|
|
43
44
|
|
|
44
45
|
[project.optional-dependencies]
|
|
@@ -15,6 +15,7 @@ from runspec_linux_core import (
|
|
|
15
15
|
container_logs,
|
|
16
16
|
detect_manager,
|
|
17
17
|
disk_usage,
|
|
18
|
+
enable_passwordless_sudo,
|
|
18
19
|
find_large_files,
|
|
19
20
|
install_package,
|
|
20
21
|
journalctl,
|
|
@@ -46,6 +47,7 @@ __all__ = [
|
|
|
46
47
|
"container_logs",
|
|
47
48
|
"detect_manager",
|
|
48
49
|
"disk_usage",
|
|
50
|
+
"enable_passwordless_sudo",
|
|
49
51
|
"find_large_files",
|
|
50
52
|
"install_package",
|
|
51
53
|
"journalctl",
|
|
@@ -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]
|
|
@@ -335,12 +336,14 @@ serve = ["local"]
|
|
|
335
336
|
description = "Refresh the package index and upgrade all installed packages (needs root)"
|
|
336
337
|
autonomy = "confirm"
|
|
337
338
|
output = "json"
|
|
339
|
+
run_as = "root"
|
|
338
340
|
|
|
339
341
|
[install-package]
|
|
340
342
|
serve = ["local"]
|
|
341
343
|
description = "Install one or more packages by name (needs root)"
|
|
342
344
|
autonomy = "confirm"
|
|
343
345
|
output = "json"
|
|
346
|
+
run_as = "root"
|
|
344
347
|
|
|
345
348
|
[install-package.args.package]
|
|
346
349
|
type = "str"
|
|
@@ -352,6 +355,7 @@ serve = ["local"]
|
|
|
352
355
|
description = "Remove one or more packages by name (needs root)"
|
|
353
356
|
autonomy = "confirm"
|
|
354
357
|
output = "json"
|
|
358
|
+
run_as = "root"
|
|
355
359
|
|
|
356
360
|
[remove-package.args.package]
|
|
357
361
|
type = "str"
|
|
@@ -365,6 +369,7 @@ serve = ["local"]
|
|
|
365
369
|
description = "Reboot the host, optionally after a delay in minutes (needs root)"
|
|
366
370
|
autonomy = "confirm"
|
|
367
371
|
output = "json"
|
|
372
|
+
run_as = "root"
|
|
368
373
|
|
|
369
374
|
[reboot-host.args.delay-minutes]
|
|
370
375
|
type = "int"
|
|
@@ -372,6 +377,34 @@ short = "-d"
|
|
|
372
377
|
description = "Minutes to wait before rebooting (0 = immediately)"
|
|
373
378
|
default = 0
|
|
374
379
|
|
|
380
|
+
# ── Privilege bootstrap ───────────────────────────────────────────────────────
|
|
381
|
+
|
|
382
|
+
# One-time setup so the run_as="root" runnables above need no password. Itself
|
|
383
|
+
# escalates with the supplied password via `sudo -S` (read from stdin, never
|
|
384
|
+
# argv), so it must NOT declare run_as. Validated with visudo before + after
|
|
385
|
+
# install and rolled back on failure.
|
|
386
|
+
[enable-passwordless-sudo]
|
|
387
|
+
serve = ["local"]
|
|
388
|
+
description = "Install a validated /etc/sudoers.d drop-in so run_as='root' runnables need no password (one-time bootstrap)"
|
|
389
|
+
autonomy = "confirm"
|
|
390
|
+
output = "json"
|
|
391
|
+
|
|
392
|
+
[enable-passwordless-sudo.args.password]
|
|
393
|
+
type = "password"
|
|
394
|
+
description = "Your sudo password — sent over the secret channel and piped to 'sudo -S'; never appears in argv, ps, or logs"
|
|
395
|
+
|
|
396
|
+
[enable-passwordless-sudo.args.user]
|
|
397
|
+
type = "str"
|
|
398
|
+
short = "-u"
|
|
399
|
+
description = "User to grant passwordless sudo (defaults to the current login user)"
|
|
400
|
+
required = false
|
|
401
|
+
|
|
402
|
+
[enable-passwordless-sudo.args.scope]
|
|
403
|
+
type = "choice"
|
|
404
|
+
options = ["scoped", "all"]
|
|
405
|
+
default = "scoped"
|
|
406
|
+
description = "scoped = NOPASSWD only for the runspec venv runnables (manual/CLI runs); all = NOPASSWD: ALL (also covers agent-driven runs)"
|
|
407
|
+
|
|
375
408
|
# ── TCP interfaces (nc-command) ────────────────────────────────────────────────
|
|
376
409
|
|
|
377
410
|
[nc-command]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import runspec as rs
|
|
5
|
+
from runspec_linux_core import enable_passwordless_sudo
|
|
6
|
+
from runspec_linux_core.errors import CommandError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main_enable_passwordless_sudo() -> None:
|
|
10
|
+
spec = rs.parse("enable-passwordless-sudo")
|
|
11
|
+
password = spec.password.value
|
|
12
|
+
user = spec.user.value
|
|
13
|
+
scope = spec.scope.value
|
|
14
|
+
try:
|
|
15
|
+
print(json.dumps(enable_passwordless_sudo(password, user, scope)))
|
|
16
|
+
except CommandError as e:
|
|
17
|
+
# Auth failure / sudoers validation failure (rolled back).
|
|
18
|
+
print(json.dumps({"installed": False, "error": str(e)}))
|
|
19
|
+
sys.exit(1)
|
|
20
|
+
except Exception as e:
|
|
21
|
+
# Includes ValueError (bad scope/user) and ToolNotFoundError (no sudo/visudo).
|
|
22
|
+
print(json.dumps({"error": str(e)}))
|
|
23
|
+
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
|