runspec-linux 0.1.3__tar.gz → 0.3.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.
- {runspec_linux-0.1.3 → runspec_linux-0.3.0}/.gitignore +3 -0
- runspec_linux-0.3.0/CHANGELOG.md +99 -0
- {runspec_linux-0.1.3 → runspec_linux-0.3.0}/PKG-INFO +2 -1
- {runspec_linux-0.1.3 → runspec_linux-0.3.0}/pyproject.toml +8 -1
- runspec_linux-0.3.0/runspec_linux/__init__.py +70 -0
- runspec_linux-0.3.0/runspec_linux/containers.py +42 -0
- runspec_linux-0.3.0/runspec_linux/files.py +28 -0
- runspec_linux-0.3.0/runspec_linux/logs.py +52 -0
- runspec_linux-0.3.0/runspec_linux/nc_command.py +38 -0
- runspec_linux-0.3.0/runspec_linux/network.py +34 -0
- runspec_linux-0.3.0/runspec_linux/packages.py +74 -0
- runspec_linux-0.3.0/runspec_linux/power.py +20 -0
- {runspec_linux-0.1.3 → runspec_linux-0.3.0}/runspec_linux/runspec.toml +73 -0
- runspec_linux-0.3.0/runspec_linux/security.py +24 -0
- runspec_linux-0.3.0/runspec_linux/services.py +40 -0
- runspec_linux-0.3.0/runspec_linux/system_info.py +43 -0
- runspec_linux-0.1.3/CHANGELOG.md +0 -51
- runspec_linux-0.1.3/runspec_linux/__init__.py +0 -3
- runspec_linux-0.1.3/runspec_linux/containers.py +0 -117
- runspec_linux-0.1.3/runspec_linux/files.py +0 -72
- runspec_linux-0.1.3/runspec_linux/logs.py +0 -81
- runspec_linux-0.1.3/runspec_linux/nc_command.py +0 -65
- runspec_linux-0.1.3/runspec_linux/network.py +0 -159
- runspec_linux-0.1.3/runspec_linux/security.py +0 -70
- runspec_linux-0.1.3/runspec_linux/services.py +0 -119
- runspec_linux-0.1.3/runspec_linux/system_info.py +0 -140
- {runspec_linux-0.1.3 → runspec_linux-0.3.0}/tests/__init__.py +0 -0
- {runspec_linux-0.1.3 → runspec_linux-0.3.0}/tests/test_nc_send.py +0 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# runspec-linux Changelog
|
|
2
|
+
|
|
3
|
+
## [0.3.0] — 2026-06-20
|
|
4
|
+
|
|
5
|
+
Add 6 runnables for managing a fleet's packages and power state from
|
|
6
|
+
runspec-console — thin wrappers over the new `runspec-linux-core` 0.2.0 helpers
|
|
7
|
+
(dependency floor bumped to `>=0.2.0`). They work across the common distro
|
|
8
|
+
families (apt/dnf/yum/zypper/pacman).
|
|
9
|
+
|
|
10
|
+
**Packages**
|
|
11
|
+
|
|
12
|
+
- `list-packages` (autonomous) — list installed packages, optional `--filter`
|
|
13
|
+
substring and `--limit`.
|
|
14
|
+
- `list-upgrades` (autonomous) — list packages with an available upgrade;
|
|
15
|
+
refreshes the index first, or pass `--no-refresh` to read the cache without
|
|
16
|
+
root.
|
|
17
|
+
- `upgrade-packages` (confirm) — refresh the index and upgrade everything.
|
|
18
|
+
- `install-package` (confirm) — install one or more packages (`--package`,
|
|
19
|
+
space-separated for several).
|
|
20
|
+
- `remove-package` (confirm) — remove one or more packages.
|
|
21
|
+
|
|
22
|
+
**Power**
|
|
23
|
+
|
|
24
|
+
- `reboot-host` (confirm) — reboot the host now, or schedule with
|
|
25
|
+
`--delay-minutes`.
|
|
26
|
+
|
|
27
|
+
The read-only runnables are `autonomous`; the state-changing ones are `confirm`,
|
|
28
|
+
matching `restart-service` / `restart-container`. Missing package manager / a
|
|
29
|
+
non-root failure returns structured JSON (`{... false, "error": ...}`) and a
|
|
30
|
+
non-zero exit rather than a stack trace; package names are validated before they
|
|
31
|
+
reach the package manager.
|
|
32
|
+
|
|
33
|
+
## [0.2.0] — 2026-06-09
|
|
34
|
+
|
|
35
|
+
Split the pure logic out into a new dependency-free package,
|
|
36
|
+
**`runspec-linux-core`**, and make `runspec-linux` depend on it. Every runnable
|
|
37
|
+
module is now a thin wrapper: it parses args via `runspec`, calls the matching
|
|
38
|
+
`runspec_linux_core` function, and renders the JSON/exit behaviour. The helper
|
|
39
|
+
functions are re-exported from `runspec_linux` for back-compat
|
|
40
|
+
(`from runspec_linux import nc_send` and friends still work), and
|
|
41
|
+
`runspec_linux.nc_command.nc_send` is preserved.
|
|
42
|
+
|
|
43
|
+
Why: importing a helper (e.g. `nc_send`) previously required installing
|
|
44
|
+
`runspec-linux`, which put all 21 console scripts on PATH and surfaced all 21
|
|
45
|
+
runnables in `runspec local` / `runspec serve`. Code that only wants the
|
|
46
|
+
importable helpers can now depend on `runspec-linux-core` instead — it has no
|
|
47
|
+
`runspec` dependency, no `runspec.toml`, and no entry points, so it surfaces
|
|
48
|
+
nothing. No behaviour change for the runnables themselves; JSON output and exit
|
|
49
|
+
codes are unchanged.
|
|
50
|
+
|
|
51
|
+
## [0.1.3] — 2026-06-04
|
|
52
|
+
|
|
53
|
+
Switch `[config.logging]` to `store = "per-run"` so every runnable writes one
|
|
54
|
+
log file per invocation (`{runnable}.{utc-ts}.{run_id}.log`) instead of a single
|
|
55
|
+
shared rotating `{runnable}.log`. These runnables are meant to be installed into
|
|
56
|
+
shared venvs and run by multiple users (and in parallel); per-invocation files
|
|
57
|
+
are multi-writer safe — no in-process rotation and no contention over one file.
|
|
58
|
+
Use `runspec logs` to view, compact, and prune. The `rotate`/`keep` keys (inert
|
|
59
|
+
under per-run) are dropped. Bumps the `runspec` dependency floor to `>=0.27.0`,
|
|
60
|
+
where `store = "per-run"` was introduced.
|
|
61
|
+
|
|
62
|
+
## [0.1.2] — 2026-06-02
|
|
63
|
+
|
|
64
|
+
Lower the Python floor to 3.10 to match the core `runspec` library (no
|
|
65
|
+
3.11-only features are used). Bump the `runspec` dependency floor to
|
|
66
|
+
`>=0.23.0`.
|
|
67
|
+
|
|
68
|
+
## [0.1.1] — 2026-05-28
|
|
69
|
+
|
|
70
|
+
Enable `[config.logging]` for all 21 runnables. Each invocation now writes a
|
|
71
|
+
JSON audit record to `{venv}/logs/{runnable}.log` (rotates at 10 MB, keeps 14
|
|
72
|
+
backups) and emits a one-line run summary to stderr. The auto-added `--debug`
|
|
73
|
+
and `--no-summary` flags are available on every runnable. No code changes —
|
|
74
|
+
existing `print(json.dumps(...))` payloads are captured into the audit log via
|
|
75
|
+
the print-capture mechanism; stdout stays clean for pipe consumers.
|
|
76
|
+
|
|
77
|
+
## [0.1.0] — 2026-05-27
|
|
78
|
+
|
|
79
|
+
Initial release.
|
|
80
|
+
|
|
81
|
+
21 runnables covering Linux system administration:
|
|
82
|
+
|
|
83
|
+
**System monitoring** — `system-info`, `disk-usage`, `check-memory`, `list-processes`
|
|
84
|
+
|
|
85
|
+
**Services** — `check-service`, `list-services`, `restart-service`
|
|
86
|
+
|
|
87
|
+
**Logs** — `tail-log`, `search-log`, `journalctl`
|
|
88
|
+
|
|
89
|
+
**Network** — `ping-host`, `check-port`, `show-connections`
|
|
90
|
+
|
|
91
|
+
**Files** — `find-large-files`, `backup-files`
|
|
92
|
+
|
|
93
|
+
**Security** — `last-logins`, `who`
|
|
94
|
+
|
|
95
|
+
**Containers** — `list-containers`, `container-logs`, `restart-container`
|
|
96
|
+
|
|
97
|
+
**TCP interfaces** — `nc-command` (also exports `nc_send()` as a public Python API for wrapper runnables)
|
|
98
|
+
|
|
99
|
+
All read-only runnables are `autonomy = "autonomous"`. State-changing runnables (`restart-service`, `backup-files`, `restart-container`) are `autonomy = "confirm"`. Missing tools (`docker`, `systemctl`, `journalctl`) return a structured JSON error rather than a stack trace.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runspec-linux
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Linux system admin runnables for runspec
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: runspec-linux-core>=0.2.0
|
|
6
7
|
Requires-Dist: runspec>=0.27.0
|
|
7
8
|
Provides-Extra: dev
|
|
8
9
|
Requires-Dist: mypy; extra == 'dev'
|
|
@@ -4,11 +4,12 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "runspec-linux"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
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.2.0",
|
|
12
13
|
]
|
|
13
14
|
|
|
14
15
|
[project.scripts]
|
|
@@ -32,6 +33,12 @@ who = "runspec_linux.security:main_who"
|
|
|
32
33
|
list-containers = "runspec_linux.containers:main_list_containers"
|
|
33
34
|
container-logs = "runspec_linux.containers:main_container_logs"
|
|
34
35
|
restart-container = "runspec_linux.containers:main_restart_container"
|
|
36
|
+
list-packages = "runspec_linux.packages:main_list_packages"
|
|
37
|
+
list-upgrades = "runspec_linux.packages:main_list_upgrades"
|
|
38
|
+
upgrade-packages = "runspec_linux.packages:main_upgrade_packages"
|
|
39
|
+
install-package = "runspec_linux.packages:main_install_package"
|
|
40
|
+
remove-package = "runspec_linux.packages:main_remove_package"
|
|
41
|
+
reboot-host = "runspec_linux.power:main_reboot_host"
|
|
35
42
|
nc-command = "runspec_linux.nc_command:main"
|
|
36
43
|
|
|
37
44
|
[project.optional-dependencies]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""runspec-linux — Linux system-admin runnables.
|
|
2
|
+
|
|
3
|
+
The pure logic lives in the dependency-free ``runspec-linux-core`` package; the
|
|
4
|
+
modules here wrap each helper in a runspec runnable. The helper functions are
|
|
5
|
+
re-exported for convenience and back-compat (e.g. ``from runspec_linux import
|
|
6
|
+
nc_send``). To import the helpers *without* surfacing any runnables in a venv,
|
|
7
|
+
depend on ``runspec-linux-core`` directly instead.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from runspec_linux_core import (
|
|
11
|
+
backup_files,
|
|
12
|
+
check_memory,
|
|
13
|
+
check_port,
|
|
14
|
+
check_service,
|
|
15
|
+
container_logs,
|
|
16
|
+
detect_manager,
|
|
17
|
+
disk_usage,
|
|
18
|
+
find_large_files,
|
|
19
|
+
install_package,
|
|
20
|
+
journalctl,
|
|
21
|
+
last_logins,
|
|
22
|
+
list_containers,
|
|
23
|
+
list_packages,
|
|
24
|
+
list_processes,
|
|
25
|
+
list_services,
|
|
26
|
+
list_upgrades,
|
|
27
|
+
nc_send,
|
|
28
|
+
ping_host,
|
|
29
|
+
reboot_host,
|
|
30
|
+
remove_package,
|
|
31
|
+
restart_container,
|
|
32
|
+
restart_service,
|
|
33
|
+
search_log,
|
|
34
|
+
show_connections,
|
|
35
|
+
system_info,
|
|
36
|
+
tail_log,
|
|
37
|
+
upgrade_packages,
|
|
38
|
+
who,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
"backup_files",
|
|
43
|
+
"check_memory",
|
|
44
|
+
"check_port",
|
|
45
|
+
"check_service",
|
|
46
|
+
"container_logs",
|
|
47
|
+
"detect_manager",
|
|
48
|
+
"disk_usage",
|
|
49
|
+
"find_large_files",
|
|
50
|
+
"install_package",
|
|
51
|
+
"journalctl",
|
|
52
|
+
"last_logins",
|
|
53
|
+
"list_containers",
|
|
54
|
+
"list_packages",
|
|
55
|
+
"list_processes",
|
|
56
|
+
"list_services",
|
|
57
|
+
"list_upgrades",
|
|
58
|
+
"nc_send",
|
|
59
|
+
"ping_host",
|
|
60
|
+
"reboot_host",
|
|
61
|
+
"remove_package",
|
|
62
|
+
"restart_container",
|
|
63
|
+
"restart_service",
|
|
64
|
+
"search_log",
|
|
65
|
+
"show_connections",
|
|
66
|
+
"system_info",
|
|
67
|
+
"tail_log",
|
|
68
|
+
"upgrade_packages",
|
|
69
|
+
"who",
|
|
70
|
+
]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import runspec as rs
|
|
5
|
+
from runspec_linux_core import container_logs, list_containers, restart_container
|
|
6
|
+
from runspec_linux_core.errors import CommandError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main_list_containers() -> None:
|
|
10
|
+
spec = rs.parse("list-containers")
|
|
11
|
+
include_all = spec.all.value
|
|
12
|
+
try:
|
|
13
|
+
print(json.dumps(list_containers(include_all)))
|
|
14
|
+
except Exception as e:
|
|
15
|
+
# Includes ToolNotFoundError (docker absent) and CommandError (docker ps failed).
|
|
16
|
+
print(json.dumps({"error": str(e)}))
|
|
17
|
+
sys.exit(1)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main_container_logs() -> None:
|
|
21
|
+
spec = rs.parse("container-logs")
|
|
22
|
+
container = spec.container.value
|
|
23
|
+
lines = spec.lines.value
|
|
24
|
+
try:
|
|
25
|
+
print(json.dumps(container_logs(container, lines)))
|
|
26
|
+
except Exception as e:
|
|
27
|
+
print(json.dumps({"error": str(e)}))
|
|
28
|
+
sys.exit(1)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def main_restart_container() -> None:
|
|
32
|
+
spec = rs.parse("restart-container")
|
|
33
|
+
container = spec.container.value
|
|
34
|
+
try:
|
|
35
|
+
print(json.dumps(restart_container(container)))
|
|
36
|
+
except CommandError as e:
|
|
37
|
+
print(json.dumps({"container": container, "restarted": False, "error": str(e)}))
|
|
38
|
+
sys.exit(1)
|
|
39
|
+
except Exception as e:
|
|
40
|
+
# Includes ToolNotFoundError (docker absent).
|
|
41
|
+
print(json.dumps({"error": str(e)}))
|
|
42
|
+
sys.exit(1)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import runspec as rs
|
|
5
|
+
from runspec_linux_core import backup_files, find_large_files
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main_find_large_files() -> None:
|
|
9
|
+
spec = rs.parse("find-large-files")
|
|
10
|
+
search_path = str(spec.path.value)
|
|
11
|
+
min_mb = spec.min_mb.value
|
|
12
|
+
limit = spec.limit.value
|
|
13
|
+
try:
|
|
14
|
+
print(json.dumps(find_large_files(search_path, min_mb, limit)))
|
|
15
|
+
except Exception as e:
|
|
16
|
+
print(json.dumps({"error": str(e)}))
|
|
17
|
+
sys.exit(1)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main_backup_files() -> None:
|
|
21
|
+
spec = rs.parse("backup-files")
|
|
22
|
+
source = str(spec.source.value)
|
|
23
|
+
destination = str(spec.destination.value)
|
|
24
|
+
try:
|
|
25
|
+
print(json.dumps(backup_files(source, destination)))
|
|
26
|
+
except Exception as e:
|
|
27
|
+
print(json.dumps({"error": str(e)}))
|
|
28
|
+
sys.exit(1)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
import runspec as rs
|
|
7
|
+
from runspec_linux_core import journalctl, search_log, tail_log
|
|
8
|
+
from runspec_linux_core.errors import ToolNotFoundError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main_tail_log() -> None:
|
|
12
|
+
spec = rs.parse("tail-log")
|
|
13
|
+
file_path = str(spec.file.value)
|
|
14
|
+
lines = spec.lines.value
|
|
15
|
+
try:
|
|
16
|
+
print(json.dumps(tail_log(file_path, lines)))
|
|
17
|
+
except OSError as e:
|
|
18
|
+
print(json.dumps({"error": str(e), "file": file_path}))
|
|
19
|
+
sys.exit(1)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main_search_log() -> None:
|
|
23
|
+
spec = rs.parse("search-log")
|
|
24
|
+
file_path = str(spec.file.value)
|
|
25
|
+
pattern = spec.pattern.value
|
|
26
|
+
limit = spec.limit.value
|
|
27
|
+
try:
|
|
28
|
+
print(json.dumps(search_log(file_path, pattern, limit)))
|
|
29
|
+
except OSError as e:
|
|
30
|
+
print(json.dumps({"error": str(e), "file": file_path}))
|
|
31
|
+
sys.exit(1)
|
|
32
|
+
except re.error as e:
|
|
33
|
+
print(json.dumps({"error": f"Invalid regex: {e}", "pattern": pattern}))
|
|
34
|
+
sys.exit(1)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def main_journalctl() -> None:
|
|
38
|
+
spec = rs.parse("journalctl")
|
|
39
|
+
unit = spec.unit.value
|
|
40
|
+
lines = spec.lines.value
|
|
41
|
+
since = spec.since.value
|
|
42
|
+
try:
|
|
43
|
+
print(json.dumps(journalctl(unit, lines, since)))
|
|
44
|
+
except ToolNotFoundError as e:
|
|
45
|
+
print(json.dumps({"error": str(e)}))
|
|
46
|
+
sys.exit(1)
|
|
47
|
+
except subprocess.CalledProcessError as e:
|
|
48
|
+
print(json.dumps({"error": e.stderr.strip(), "unit": unit}))
|
|
49
|
+
sys.exit(1)
|
|
50
|
+
except Exception as e:
|
|
51
|
+
print(json.dumps({"error": str(e)}))
|
|
52
|
+
sys.exit(1)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import runspec as rs
|
|
5
|
+
|
|
6
|
+
# Re-exported for back-compat: callers historically did
|
|
7
|
+
# `from runspec_linux.nc_command import nc_send`. The implementation now lives
|
|
8
|
+
# in the dependency-free runspec-linux-core package.
|
|
9
|
+
from runspec_linux_core import nc_send
|
|
10
|
+
|
|
11
|
+
__all__ = ["main", "nc_send"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main() -> None:
|
|
15
|
+
spec = rs.parse("nc-command")
|
|
16
|
+
host = spec.host.value
|
|
17
|
+
port = spec.port.value
|
|
18
|
+
command = spec.command.value
|
|
19
|
+
wait = spec.wait.value
|
|
20
|
+
read_timeout = spec.read_timeout.value
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
response = nc_send(host, port, command, wait=wait, read_timeout=read_timeout)
|
|
24
|
+
lines = response.splitlines()
|
|
25
|
+
print(
|
|
26
|
+
json.dumps(
|
|
27
|
+
{
|
|
28
|
+
"host": host,
|
|
29
|
+
"port": port,
|
|
30
|
+
"command": command,
|
|
31
|
+
"stdout": response,
|
|
32
|
+
"lines": lines,
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
except OSError as e:
|
|
37
|
+
print(json.dumps({"error": str(e), "host": host, "port": port}))
|
|
38
|
+
sys.exit(1)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import runspec as rs
|
|
5
|
+
from runspec_linux_core import check_port, ping_host, show_connections
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main_ping_host() -> None:
|
|
9
|
+
spec = rs.parse("ping-host")
|
|
10
|
+
host = spec.host.value
|
|
11
|
+
count = spec.count.value
|
|
12
|
+
try:
|
|
13
|
+
print(json.dumps(ping_host(host, count)))
|
|
14
|
+
except Exception as e:
|
|
15
|
+
print(json.dumps({"error": str(e), "host": host}))
|
|
16
|
+
sys.exit(1)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main_check_port() -> None:
|
|
20
|
+
spec = rs.parse("check-port")
|
|
21
|
+
host = spec.host.value
|
|
22
|
+
port = spec.port.value
|
|
23
|
+
timeout = spec.timeout.value
|
|
24
|
+
print(json.dumps(check_port(host, port, timeout)))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def main_show_connections() -> None:
|
|
28
|
+
spec = rs.parse("show-connections")
|
|
29
|
+
state_filter = spec.state.value
|
|
30
|
+
try:
|
|
31
|
+
print(json.dumps(show_connections(state_filter)))
|
|
32
|
+
except Exception as e:
|
|
33
|
+
print(json.dumps({"error": str(e)}))
|
|
34
|
+
sys.exit(1)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import runspec as rs
|
|
5
|
+
from runspec_linux_core import (
|
|
6
|
+
install_package,
|
|
7
|
+
list_packages,
|
|
8
|
+
list_upgrades,
|
|
9
|
+
remove_package,
|
|
10
|
+
upgrade_packages,
|
|
11
|
+
)
|
|
12
|
+
from runspec_linux_core.errors import CommandError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main_list_packages() -> None:
|
|
16
|
+
spec = rs.parse("list-packages")
|
|
17
|
+
name_filter = spec.filter.value
|
|
18
|
+
limit = spec.limit.value
|
|
19
|
+
try:
|
|
20
|
+
print(json.dumps(list_packages(name_filter, limit)))
|
|
21
|
+
except Exception as e:
|
|
22
|
+
# Includes ToolNotFoundError (no package manager) and CommandError.
|
|
23
|
+
print(json.dumps({"error": str(e)}))
|
|
24
|
+
sys.exit(1)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def main_list_upgrades() -> None:
|
|
28
|
+
spec = rs.parse("list-upgrades")
|
|
29
|
+
try:
|
|
30
|
+
print(json.dumps(list_upgrades(refresh=not spec.no_refresh.value)))
|
|
31
|
+
except Exception as e:
|
|
32
|
+
print(json.dumps({"error": str(e)}))
|
|
33
|
+
sys.exit(1)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def main_upgrade_packages() -> None:
|
|
37
|
+
rs.parse("upgrade-packages")
|
|
38
|
+
try:
|
|
39
|
+
print(json.dumps(upgrade_packages()))
|
|
40
|
+
except CommandError as e:
|
|
41
|
+
print(json.dumps({"upgraded": False, "error": str(e)}))
|
|
42
|
+
sys.exit(1)
|
|
43
|
+
except Exception as e:
|
|
44
|
+
# Includes ToolNotFoundError (no package manager).
|
|
45
|
+
print(json.dumps({"error": str(e)}))
|
|
46
|
+
sys.exit(1)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def main_install_package() -> None:
|
|
50
|
+
spec = rs.parse("install-package")
|
|
51
|
+
package = spec.package.value
|
|
52
|
+
try:
|
|
53
|
+
print(json.dumps(install_package(package)))
|
|
54
|
+
except CommandError as e:
|
|
55
|
+
print(json.dumps({"package": package, "installed": False, "error": str(e)}))
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
except Exception as e:
|
|
58
|
+
# Includes ValueError (bad name) and ToolNotFoundError (no package manager).
|
|
59
|
+
print(json.dumps({"error": str(e)}))
|
|
60
|
+
sys.exit(1)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def main_remove_package() -> None:
|
|
64
|
+
spec = rs.parse("remove-package")
|
|
65
|
+
package = spec.package.value
|
|
66
|
+
try:
|
|
67
|
+
print(json.dumps(remove_package(package)))
|
|
68
|
+
except CommandError as e:
|
|
69
|
+
print(json.dumps({"package": package, "removed": False, "error": str(e)}))
|
|
70
|
+
sys.exit(1)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
# Includes ValueError (bad name) and ToolNotFoundError (no package manager).
|
|
73
|
+
print(json.dumps({"error": str(e)}))
|
|
74
|
+
sys.exit(1)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import runspec as rs
|
|
5
|
+
from runspec_linux_core import reboot_host
|
|
6
|
+
from runspec_linux_core.errors import CommandError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main_reboot_host() -> None:
|
|
10
|
+
spec = rs.parse("reboot-host")
|
|
11
|
+
delay_minutes = spec.delay_minutes.value
|
|
12
|
+
try:
|
|
13
|
+
print(json.dumps(reboot_host(delay_minutes)))
|
|
14
|
+
except CommandError as e:
|
|
15
|
+
print(json.dumps({"scheduled": False, "error": str(e)}))
|
|
16
|
+
sys.exit(1)
|
|
17
|
+
except Exception as e:
|
|
18
|
+
# Includes ToolNotFoundError (no shutdown/systemctl).
|
|
19
|
+
print(json.dumps({"error": str(e)}))
|
|
20
|
+
sys.exit(1)
|
|
@@ -299,6 +299,79 @@ type = "str"
|
|
|
299
299
|
short = "-c"
|
|
300
300
|
description = "Container name or ID"
|
|
301
301
|
|
|
302
|
+
# ── Packages ──────────────────────────────────────────────────────────────────
|
|
303
|
+
|
|
304
|
+
[list-packages]
|
|
305
|
+
serve = ["local"]
|
|
306
|
+
description = "List installed packages (apt/dnf/yum/zypper/pacman), optionally filtered by name"
|
|
307
|
+
autonomy = "autonomous"
|
|
308
|
+
output = "json"
|
|
309
|
+
|
|
310
|
+
[list-packages.args.filter]
|
|
311
|
+
type = "str"
|
|
312
|
+
short = "-f"
|
|
313
|
+
description = "Only list packages whose name contains this string"
|
|
314
|
+
required = false
|
|
315
|
+
|
|
316
|
+
[list-packages.args.limit]
|
|
317
|
+
type = "int"
|
|
318
|
+
short = "-n"
|
|
319
|
+
description = "Maximum number of packages to return (sorted by name)"
|
|
320
|
+
default = 100
|
|
321
|
+
|
|
322
|
+
[list-upgrades]
|
|
323
|
+
serve = ["local"]
|
|
324
|
+
description = "List packages with an available upgrade (refreshes the package index first)"
|
|
325
|
+
autonomy = "autonomous"
|
|
326
|
+
output = "json"
|
|
327
|
+
|
|
328
|
+
[list-upgrades.args.no-refresh]
|
|
329
|
+
type = "flag"
|
|
330
|
+
description = "Read the cached package index instead of refreshing it first (no root needed)"
|
|
331
|
+
default = false
|
|
332
|
+
|
|
333
|
+
[upgrade-packages]
|
|
334
|
+
serve = ["local"]
|
|
335
|
+
description = "Refresh the package index and upgrade all installed packages (needs root)"
|
|
336
|
+
autonomy = "confirm"
|
|
337
|
+
output = "json"
|
|
338
|
+
|
|
339
|
+
[install-package]
|
|
340
|
+
serve = ["local"]
|
|
341
|
+
description = "Install one or more packages by name (needs root)"
|
|
342
|
+
autonomy = "confirm"
|
|
343
|
+
output = "json"
|
|
344
|
+
|
|
345
|
+
[install-package.args.package]
|
|
346
|
+
type = "str"
|
|
347
|
+
short = "-p"
|
|
348
|
+
description = "Package name to install (space-separated for several)"
|
|
349
|
+
|
|
350
|
+
[remove-package]
|
|
351
|
+
serve = ["local"]
|
|
352
|
+
description = "Remove one or more packages by name (needs root)"
|
|
353
|
+
autonomy = "confirm"
|
|
354
|
+
output = "json"
|
|
355
|
+
|
|
356
|
+
[remove-package.args.package]
|
|
357
|
+
type = "str"
|
|
358
|
+
short = "-p"
|
|
359
|
+
description = "Package name to remove (space-separated for several)"
|
|
360
|
+
|
|
361
|
+
# ── Power ─────────────────────────────────────────────────────────────────────
|
|
362
|
+
|
|
363
|
+
[reboot-host]
|
|
364
|
+
serve = ["local"]
|
|
365
|
+
description = "Reboot the host, optionally after a delay in minutes (needs root)"
|
|
366
|
+
autonomy = "confirm"
|
|
367
|
+
output = "json"
|
|
368
|
+
|
|
369
|
+
[reboot-host.args.delay-minutes]
|
|
370
|
+
type = "int"
|
|
371
|
+
short = "-d"
|
|
372
|
+
description = "Minutes to wait before rebooting (0 = immediately)"
|
|
373
|
+
default = 0
|
|
374
|
+
|
|
302
375
|
# ── TCP interfaces (nc-command) ────────────────────────────────────────────────
|
|
303
376
|
|
|
304
377
|
[nc-command]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import runspec as rs
|
|
5
|
+
from runspec_linux_core import last_logins, who
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main_last_logins() -> None:
|
|
9
|
+
spec = rs.parse("last-logins")
|
|
10
|
+
limit = spec.limit.value
|
|
11
|
+
try:
|
|
12
|
+
print(json.dumps(last_logins(limit)))
|
|
13
|
+
except Exception as e:
|
|
14
|
+
print(json.dumps({"error": str(e)}))
|
|
15
|
+
sys.exit(1)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main_who() -> None:
|
|
19
|
+
rs.parse("who")
|
|
20
|
+
try:
|
|
21
|
+
print(json.dumps(who()))
|
|
22
|
+
except Exception as e:
|
|
23
|
+
print(json.dumps({"error": str(e)}))
|
|
24
|
+
sys.exit(1)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import runspec as rs
|
|
5
|
+
from runspec_linux_core import check_service, list_services, restart_service
|
|
6
|
+
from runspec_linux_core.errors import CommandError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main_check_service() -> None:
|
|
10
|
+
spec = rs.parse("check-service")
|
|
11
|
+
service = spec.service.value
|
|
12
|
+
try:
|
|
13
|
+
print(json.dumps(check_service(service)))
|
|
14
|
+
except Exception as e:
|
|
15
|
+
print(json.dumps({"error": str(e)}))
|
|
16
|
+
sys.exit(1)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main_list_services() -> None:
|
|
20
|
+
spec = rs.parse("list-services")
|
|
21
|
+
state_filter = spec.state.value
|
|
22
|
+
try:
|
|
23
|
+
print(json.dumps(list_services(state_filter)))
|
|
24
|
+
except Exception as e:
|
|
25
|
+
print(json.dumps({"error": str(e)}))
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def main_restart_service() -> None:
|
|
30
|
+
spec = rs.parse("restart-service")
|
|
31
|
+
service = spec.service.value
|
|
32
|
+
try:
|
|
33
|
+
print(json.dumps(restart_service(service)))
|
|
34
|
+
except CommandError as e:
|
|
35
|
+
print(json.dumps({"service": service, "restarted": False, "error": str(e)}))
|
|
36
|
+
sys.exit(1)
|
|
37
|
+
except Exception as e:
|
|
38
|
+
# Includes ToolNotFoundError (systemctl absent).
|
|
39
|
+
print(json.dumps({"error": str(e)}))
|
|
40
|
+
sys.exit(1)
|