runspec-linux 0.1.2__tar.gz → 0.2.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.2 → runspec_linux-0.2.0}/.gitignore +3 -0
- runspec_linux-0.2.0/CHANGELOG.md +69 -0
- {runspec_linux-0.1.2 → runspec_linux-0.2.0}/PKG-INFO +3 -2
- {runspec_linux-0.1.2 → runspec_linux-0.2.0}/pyproject.toml +3 -2
- runspec_linux-0.2.0/runspec_linux/__init__.py +56 -0
- runspec_linux-0.2.0/runspec_linux/containers.py +42 -0
- runspec_linux-0.2.0/runspec_linux/files.py +28 -0
- runspec_linux-0.2.0/runspec_linux/logs.py +52 -0
- runspec_linux-0.2.0/runspec_linux/nc_command.py +38 -0
- runspec_linux-0.2.0/runspec_linux/network.py +34 -0
- {runspec_linux-0.1.2 → runspec_linux-0.2.0}/runspec_linux/runspec.toml +4 -2
- runspec_linux-0.2.0/runspec_linux/security.py +24 -0
- runspec_linux-0.2.0/runspec_linux/services.py +40 -0
- runspec_linux-0.2.0/runspec_linux/system_info.py +43 -0
- runspec_linux-0.1.2/CHANGELOG.md +0 -40
- runspec_linux-0.1.2/runspec_linux/__init__.py +0 -3
- runspec_linux-0.1.2/runspec_linux/containers.py +0 -117
- runspec_linux-0.1.2/runspec_linux/files.py +0 -72
- runspec_linux-0.1.2/runspec_linux/logs.py +0 -81
- runspec_linux-0.1.2/runspec_linux/nc_command.py +0 -65
- runspec_linux-0.1.2/runspec_linux/network.py +0 -159
- runspec_linux-0.1.2/runspec_linux/security.py +0 -70
- runspec_linux-0.1.2/runspec_linux/services.py +0 -119
- runspec_linux-0.1.2/runspec_linux/system_info.py +0 -140
- {runspec_linux-0.1.2 → runspec_linux-0.2.0}/tests/__init__.py +0 -0
- {runspec_linux-0.1.2 → runspec_linux-0.2.0}/tests/test_nc_send.py +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# runspec-linux Changelog
|
|
2
|
+
|
|
3
|
+
## [0.2.0] — 2026-06-09
|
|
4
|
+
|
|
5
|
+
Split the pure logic out into a new dependency-free package,
|
|
6
|
+
**`runspec-linux-core`**, and make `runspec-linux` depend on it. Every runnable
|
|
7
|
+
module is now a thin wrapper: it parses args via `runspec`, calls the matching
|
|
8
|
+
`runspec_linux_core` function, and renders the JSON/exit behaviour. The helper
|
|
9
|
+
functions are re-exported from `runspec_linux` for back-compat
|
|
10
|
+
(`from runspec_linux import nc_send` and friends still work), and
|
|
11
|
+
`runspec_linux.nc_command.nc_send` is preserved.
|
|
12
|
+
|
|
13
|
+
Why: importing a helper (e.g. `nc_send`) previously required installing
|
|
14
|
+
`runspec-linux`, which put all 21 console scripts on PATH and surfaced all 21
|
|
15
|
+
runnables in `runspec local` / `runspec serve`. Code that only wants the
|
|
16
|
+
importable helpers can now depend on `runspec-linux-core` instead — it has no
|
|
17
|
+
`runspec` dependency, no `runspec.toml`, and no entry points, so it surfaces
|
|
18
|
+
nothing. No behaviour change for the runnables themselves; JSON output and exit
|
|
19
|
+
codes are unchanged.
|
|
20
|
+
|
|
21
|
+
## [0.1.3] — 2026-06-04
|
|
22
|
+
|
|
23
|
+
Switch `[config.logging]` to `store = "per-run"` so every runnable writes one
|
|
24
|
+
log file per invocation (`{runnable}.{utc-ts}.{run_id}.log`) instead of a single
|
|
25
|
+
shared rotating `{runnable}.log`. These runnables are meant to be installed into
|
|
26
|
+
shared venvs and run by multiple users (and in parallel); per-invocation files
|
|
27
|
+
are multi-writer safe — no in-process rotation and no contention over one file.
|
|
28
|
+
Use `runspec logs` to view, compact, and prune. The `rotate`/`keep` keys (inert
|
|
29
|
+
under per-run) are dropped. Bumps the `runspec` dependency floor to `>=0.27.0`,
|
|
30
|
+
where `store = "per-run"` was introduced.
|
|
31
|
+
|
|
32
|
+
## [0.1.2] — 2026-06-02
|
|
33
|
+
|
|
34
|
+
Lower the Python floor to 3.10 to match the core `runspec` library (no
|
|
35
|
+
3.11-only features are used). Bump the `runspec` dependency floor to
|
|
36
|
+
`>=0.23.0`.
|
|
37
|
+
|
|
38
|
+
## [0.1.1] — 2026-05-28
|
|
39
|
+
|
|
40
|
+
Enable `[config.logging]` for all 21 runnables. Each invocation now writes a
|
|
41
|
+
JSON audit record to `{venv}/logs/{runnable}.log` (rotates at 10 MB, keeps 14
|
|
42
|
+
backups) and emits a one-line run summary to stderr. The auto-added `--debug`
|
|
43
|
+
and `--no-summary` flags are available on every runnable. No code changes —
|
|
44
|
+
existing `print(json.dumps(...))` payloads are captured into the audit log via
|
|
45
|
+
the print-capture mechanism; stdout stays clean for pipe consumers.
|
|
46
|
+
|
|
47
|
+
## [0.1.0] — 2026-05-27
|
|
48
|
+
|
|
49
|
+
Initial release.
|
|
50
|
+
|
|
51
|
+
21 runnables covering Linux system administration:
|
|
52
|
+
|
|
53
|
+
**System monitoring** — `system-info`, `disk-usage`, `check-memory`, `list-processes`
|
|
54
|
+
|
|
55
|
+
**Services** — `check-service`, `list-services`, `restart-service`
|
|
56
|
+
|
|
57
|
+
**Logs** — `tail-log`, `search-log`, `journalctl`
|
|
58
|
+
|
|
59
|
+
**Network** — `ping-host`, `check-port`, `show-connections`
|
|
60
|
+
|
|
61
|
+
**Files** — `find-large-files`, `backup-files`
|
|
62
|
+
|
|
63
|
+
**Security** — `last-logins`, `who`
|
|
64
|
+
|
|
65
|
+
**Containers** — `list-containers`, `container-logs`, `restart-container`
|
|
66
|
+
|
|
67
|
+
**TCP interfaces** — `nc-command` (also exports `nc_send()` as a public Python API for wrapper runnables)
|
|
68
|
+
|
|
69
|
+
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,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runspec-linux
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Linux system admin runnables for runspec
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
|
-
Requires-Dist: runspec>=0.
|
|
6
|
+
Requires-Dist: runspec-linux-core>=0.1.0
|
|
7
|
+
Requires-Dist: runspec>=0.27.0
|
|
7
8
|
Provides-Extra: dev
|
|
8
9
|
Requires-Dist: mypy; extra == 'dev'
|
|
9
10
|
Requires-Dist: pytest>=8.0; 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.2.0"
|
|
8
8
|
requires-python = ">=3.10"
|
|
9
9
|
description = "Linux system admin runnables for runspec"
|
|
10
10
|
dependencies = [
|
|
11
|
-
"runspec>=0.
|
|
11
|
+
"runspec>=0.27.0",
|
|
12
|
+
"runspec-linux-core>=0.1.0",
|
|
12
13
|
]
|
|
13
14
|
|
|
14
15
|
[project.scripts]
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
disk_usage,
|
|
17
|
+
find_large_files,
|
|
18
|
+
journalctl,
|
|
19
|
+
last_logins,
|
|
20
|
+
list_containers,
|
|
21
|
+
list_processes,
|
|
22
|
+
list_services,
|
|
23
|
+
nc_send,
|
|
24
|
+
ping_host,
|
|
25
|
+
restart_container,
|
|
26
|
+
restart_service,
|
|
27
|
+
search_log,
|
|
28
|
+
show_connections,
|
|
29
|
+
system_info,
|
|
30
|
+
tail_log,
|
|
31
|
+
who,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"backup_files",
|
|
36
|
+
"check_memory",
|
|
37
|
+
"check_port",
|
|
38
|
+
"check_service",
|
|
39
|
+
"container_logs",
|
|
40
|
+
"disk_usage",
|
|
41
|
+
"find_large_files",
|
|
42
|
+
"journalctl",
|
|
43
|
+
"last_logins",
|
|
44
|
+
"list_containers",
|
|
45
|
+
"list_processes",
|
|
46
|
+
"list_services",
|
|
47
|
+
"nc_send",
|
|
48
|
+
"ping_host",
|
|
49
|
+
"restart_container",
|
|
50
|
+
"restart_service",
|
|
51
|
+
"search_log",
|
|
52
|
+
"show_connections",
|
|
53
|
+
"system_info",
|
|
54
|
+
"tail_log",
|
|
55
|
+
"who",
|
|
56
|
+
]
|
|
@@ -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 = bool(spec.all)
|
|
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 = str(spec.container)
|
|
23
|
+
lines = int(spec.lines)
|
|
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 = str(spec.container)
|
|
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)
|
|
11
|
+
min_mb = float(spec.min_mb)
|
|
12
|
+
limit = int(spec.limit)
|
|
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)
|
|
23
|
+
destination = str(spec.destination)
|
|
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)
|
|
14
|
+
lines = int(spec.lines)
|
|
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)
|
|
25
|
+
pattern = str(spec.pattern)
|
|
26
|
+
limit = int(spec.limit)
|
|
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 = str(spec.unit)
|
|
40
|
+
lines = int(spec.lines)
|
|
41
|
+
since = str(spec.since) if spec.since is not None else None
|
|
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 = str(spec.host)
|
|
17
|
+
port = int(spec.port)
|
|
18
|
+
command = str(spec.command)
|
|
19
|
+
wait = float(spec.wait)
|
|
20
|
+
read_timeout = float(spec.read_timeout)
|
|
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 = str(spec.host)
|
|
11
|
+
count = int(spec.count)
|
|
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 = str(spec.host)
|
|
22
|
+
port = int(spec.port)
|
|
23
|
+
timeout = float(spec.timeout)
|
|
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 = str(spec.state)
|
|
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)
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
autonomy-default = "autonomous"
|
|
3
3
|
|
|
4
4
|
[config.logging]
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
# Per-invocation log files: one {runnable}.{utc-ts}.{run_id}.log per run.
|
|
6
|
+
# Multi-writer safe for shared venvs — no in-process rotation (use
|
|
7
|
+
# `runspec logs` to view/compact/prune). rotate/keep are inert under per-run.
|
|
8
|
+
store = "per-run"
|
|
7
9
|
|
|
8
10
|
# ── System monitoring ─────────────────────────────────────────────────────────
|
|
9
11
|
|
|
@@ -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 = int(spec.limit)
|
|
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 = str(spec.service)
|
|
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 = str(spec.state)
|
|
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 = str(spec.service)
|
|
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)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import runspec as rs
|
|
5
|
+
from runspec_linux_core import check_memory, disk_usage, list_processes, system_info
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main_system_info() -> None:
|
|
9
|
+
rs.parse("system-info")
|
|
10
|
+
try:
|
|
11
|
+
print(json.dumps(system_info()))
|
|
12
|
+
except Exception as e:
|
|
13
|
+
print(json.dumps({"error": str(e)}))
|
|
14
|
+
sys.exit(1)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main_disk_usage() -> None:
|
|
18
|
+
rs.parse("disk-usage")
|
|
19
|
+
try:
|
|
20
|
+
print(json.dumps(disk_usage()))
|
|
21
|
+
except Exception as e:
|
|
22
|
+
print(json.dumps({"error": str(e)}))
|
|
23
|
+
sys.exit(1)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def main_check_memory() -> None:
|
|
27
|
+
rs.parse("check-memory")
|
|
28
|
+
try:
|
|
29
|
+
print(json.dumps(check_memory()))
|
|
30
|
+
except Exception as e:
|
|
31
|
+
print(json.dumps({"error": str(e)}))
|
|
32
|
+
sys.exit(1)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def main_list_processes() -> None:
|
|
36
|
+
spec = rs.parse("list-processes")
|
|
37
|
+
name_filter = str(spec.filter) if spec.filter is not None else None
|
|
38
|
+
limit = int(spec.limit)
|
|
39
|
+
try:
|
|
40
|
+
print(json.dumps(list_processes(name_filter, limit)))
|
|
41
|
+
except Exception as e:
|
|
42
|
+
print(json.dumps({"error": str(e)}))
|
|
43
|
+
sys.exit(1)
|
runspec_linux-0.1.2/CHANGELOG.md
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# runspec-linux Changelog
|
|
2
|
-
|
|
3
|
-
## [0.1.2] — 2026-06-02
|
|
4
|
-
|
|
5
|
-
Lower the Python floor to 3.10 to match the core `runspec` library (no
|
|
6
|
-
3.11-only features are used). Bump the `runspec` dependency floor to
|
|
7
|
-
`>=0.23.0`.
|
|
8
|
-
|
|
9
|
-
## [0.1.1] — 2026-05-28
|
|
10
|
-
|
|
11
|
-
Enable `[config.logging]` for all 21 runnables. Each invocation now writes a
|
|
12
|
-
JSON audit record to `{venv}/logs/{runnable}.log` (rotates at 10 MB, keeps 14
|
|
13
|
-
backups) and emits a one-line run summary to stderr. The auto-added `--debug`
|
|
14
|
-
and `--no-summary` flags are available on every runnable. No code changes —
|
|
15
|
-
existing `print(json.dumps(...))` payloads are captured into the audit log via
|
|
16
|
-
the print-capture mechanism; stdout stays clean for pipe consumers.
|
|
17
|
-
|
|
18
|
-
## [0.1.0] — 2026-05-27
|
|
19
|
-
|
|
20
|
-
Initial release.
|
|
21
|
-
|
|
22
|
-
21 runnables covering Linux system administration:
|
|
23
|
-
|
|
24
|
-
**System monitoring** — `system-info`, `disk-usage`, `check-memory`, `list-processes`
|
|
25
|
-
|
|
26
|
-
**Services** — `check-service`, `list-services`, `restart-service`
|
|
27
|
-
|
|
28
|
-
**Logs** — `tail-log`, `search-log`, `journalctl`
|
|
29
|
-
|
|
30
|
-
**Network** — `ping-host`, `check-port`, `show-connections`
|
|
31
|
-
|
|
32
|
-
**Files** — `find-large-files`, `backup-files`
|
|
33
|
-
|
|
34
|
-
**Security** — `last-logins`, `who`
|
|
35
|
-
|
|
36
|
-
**Containers** — `list-containers`, `container-logs`, `restart-container`
|
|
37
|
-
|
|
38
|
-
**TCP interfaces** — `nc-command` (also exports `nc_send()` as a public Python API for wrapper runnables)
|
|
39
|
-
|
|
40
|
-
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,117 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import shutil
|
|
3
|
-
import subprocess
|
|
4
|
-
import sys
|
|
5
|
-
|
|
6
|
-
import runspec as rs
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def _docker_available() -> bool:
|
|
10
|
-
return shutil.which("docker") is not None
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def main_list_containers() -> None:
|
|
14
|
-
spec = rs.parse("list-containers")
|
|
15
|
-
include_all = bool(spec.all)
|
|
16
|
-
|
|
17
|
-
if not _docker_available():
|
|
18
|
-
print(json.dumps({"error": "docker not installed or not in PATH"}))
|
|
19
|
-
sys.exit(1)
|
|
20
|
-
|
|
21
|
-
try:
|
|
22
|
-
cmd = [
|
|
23
|
-
"docker",
|
|
24
|
-
"ps",
|
|
25
|
-
"--format",
|
|
26
|
-
"{{.ID}}\t{{.Image}}\t{{.Command}}\t{{.CreatedAt}}\t{{.Status}}\t{{.Names}}",
|
|
27
|
-
]
|
|
28
|
-
if include_all:
|
|
29
|
-
cmd.append("--all")
|
|
30
|
-
|
|
31
|
-
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
32
|
-
if result.returncode != 0:
|
|
33
|
-
print(json.dumps({"error": result.stderr.strip()}))
|
|
34
|
-
sys.exit(1)
|
|
35
|
-
|
|
36
|
-
rows = []
|
|
37
|
-
for line in result.stdout.strip().splitlines():
|
|
38
|
-
parts = line.split("\t")
|
|
39
|
-
if len(parts) < 6:
|
|
40
|
-
continue
|
|
41
|
-
rows.append(
|
|
42
|
-
{
|
|
43
|
-
"id": parts[0],
|
|
44
|
-
"image": parts[1],
|
|
45
|
-
"command": parts[2].strip('"'),
|
|
46
|
-
"created": parts[3],
|
|
47
|
-
"status": parts[4],
|
|
48
|
-
"name": parts[5],
|
|
49
|
-
}
|
|
50
|
-
)
|
|
51
|
-
print(json.dumps(rows))
|
|
52
|
-
except Exception as e:
|
|
53
|
-
print(json.dumps({"error": str(e)}))
|
|
54
|
-
sys.exit(1)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def main_container_logs() -> None:
|
|
58
|
-
spec = rs.parse("container-logs")
|
|
59
|
-
container = str(spec.container)
|
|
60
|
-
lines = int(spec.lines)
|
|
61
|
-
|
|
62
|
-
if not _docker_available():
|
|
63
|
-
print(json.dumps({"error": "docker not installed or not in PATH"}))
|
|
64
|
-
sys.exit(1)
|
|
65
|
-
|
|
66
|
-
try:
|
|
67
|
-
result = subprocess.run(
|
|
68
|
-
["docker", "logs", "--tail", str(lines), container],
|
|
69
|
-
capture_output=True,
|
|
70
|
-
text=True,
|
|
71
|
-
)
|
|
72
|
-
# docker logs writes to stderr by default
|
|
73
|
-
output = result.stderr if result.stderr else result.stdout
|
|
74
|
-
output_lines = output.strip().splitlines()
|
|
75
|
-
print(
|
|
76
|
-
json.dumps(
|
|
77
|
-
{
|
|
78
|
-
"container": container,
|
|
79
|
-
"lines": output_lines,
|
|
80
|
-
"count": len(output_lines),
|
|
81
|
-
}
|
|
82
|
-
)
|
|
83
|
-
)
|
|
84
|
-
except Exception as e:
|
|
85
|
-
print(json.dumps({"error": str(e)}))
|
|
86
|
-
sys.exit(1)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def main_restart_container() -> None:
|
|
90
|
-
spec = rs.parse("restart-container")
|
|
91
|
-
container = str(spec.container)
|
|
92
|
-
|
|
93
|
-
if not _docker_available():
|
|
94
|
-
print(json.dumps({"error": "docker not installed or not in PATH"}))
|
|
95
|
-
sys.exit(1)
|
|
96
|
-
|
|
97
|
-
try:
|
|
98
|
-
result = subprocess.run(
|
|
99
|
-
["docker", "restart", container],
|
|
100
|
-
capture_output=True,
|
|
101
|
-
text=True,
|
|
102
|
-
)
|
|
103
|
-
if result.returncode != 0:
|
|
104
|
-
print(
|
|
105
|
-
json.dumps(
|
|
106
|
-
{
|
|
107
|
-
"container": container,
|
|
108
|
-
"restarted": False,
|
|
109
|
-
"error": result.stderr.strip(),
|
|
110
|
-
}
|
|
111
|
-
)
|
|
112
|
-
)
|
|
113
|
-
sys.exit(1)
|
|
114
|
-
print(json.dumps({"container": container, "restarted": True}))
|
|
115
|
-
except Exception as e:
|
|
116
|
-
print(json.dumps({"error": str(e)}))
|
|
117
|
-
sys.exit(1)
|