getbased-agent-stack 0.4.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.
- {getbased_agent_stack-0.4.0/src/getbased_agent_stack.egg-info → getbased_agent_stack-0.4.1}/PKG-INFO +1 -1
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/pyproject.toml +1 -1
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/src/getbased_agent_stack/__init__.py +1 -1
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/src/getbased_agent_stack/systemd/getbased-dashboard.service +3 -7
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/src/getbased_agent_stack/systemd/getbased-rag.service +7 -8
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1/src/getbased_agent_stack.egg-info}/PKG-INFO +1 -1
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/src/getbased_agent_stack.egg-info/SOURCES.txt +1 -0
- getbased_agent_stack-0.4.1/tests/test_systemd_units.py +109 -0
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/LICENSE +0 -0
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/README.md +0 -0
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/setup.cfg +0 -0
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/src/getbased_agent_stack/cli.py +0 -0
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/src/getbased_agent_stack/env_file.py +0 -0
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/src/getbased_agent_stack/mcp_configs.py +0 -0
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/src/getbased_agent_stack/units.py +0 -0
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/src/getbased_agent_stack.egg-info/dependency_links.txt +0 -0
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/src/getbased_agent_stack.egg-info/entry_points.txt +0 -0
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/src/getbased_agent_stack.egg-info/requires.txt +0 -0
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/src/getbased_agent_stack.egg-info/top_level.txt +0 -0
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/tests/test_cli.py +0 -0
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/tests/test_env_file.py +0 -0
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/tests/test_integration.py +0 -0
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/tests/test_mcp_configs.py +0 -0
- {getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/tests/test_units.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "getbased-agent-stack"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.1"
|
|
8
8
|
description = "One-command install of the full getbased agent stack — getbased-mcp + getbased-rag + getbased-dashboard"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "GPL-3.0-only"
|
|
@@ -16,7 +16,9 @@ ExecStart=%h/.local/bin/getbased-dashboard serve
|
|
|
16
16
|
Restart=always
|
|
17
17
|
RestartSec=5s
|
|
18
18
|
|
|
19
|
-
#
|
|
19
|
+
# User-mode-compatible hardening only — see getbased-rag.service for
|
|
20
|
+
# the list of CAP_SYS_ADMIN-requiring directives that fail with
|
|
21
|
+
# 218/CAPABILITIES under `systemctl --user` and are deliberately omitted.
|
|
20
22
|
NoNewPrivileges=true
|
|
21
23
|
ProtectSystem=strict
|
|
22
24
|
ProtectHome=read-only
|
|
@@ -24,13 +26,7 @@ ProtectHome=read-only
|
|
|
24
26
|
# the shared env file when the user edits the token in the MCP tab.
|
|
25
27
|
ReadWritePaths=%h/.local/state/getbased %h/.config/getbased
|
|
26
28
|
PrivateTmp=true
|
|
27
|
-
ProtectKernelTunables=true
|
|
28
|
-
ProtectKernelModules=true
|
|
29
|
-
ProtectControlGroups=true
|
|
30
|
-
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
|
31
|
-
RestrictNamespaces=true
|
|
32
29
|
LockPersonality=true
|
|
33
|
-
RestrictRealtime=true
|
|
34
30
|
|
|
35
31
|
[Install]
|
|
36
32
|
WantedBy=default.target
|
|
@@ -22,20 +22,19 @@ ExecStart=%h/.local/bin/lens serve
|
|
|
22
22
|
Restart=always
|
|
23
23
|
RestartSec=5s
|
|
24
24
|
|
|
25
|
-
#
|
|
25
|
+
# User-mode-compatible hardening only. Directives that require
|
|
26
|
+
# CAP_SYS_ADMIN (ProtectKernelTunables, ProtectKernelModules,
|
|
27
|
+
# ProtectControlGroups, RestrictNamespaces, RestrictAddressFamilies,
|
|
28
|
+
# RestrictRealtime, MemoryDenyWriteExecute) fail with 218/CAPABILITIES
|
|
29
|
+
# under `systemctl --user` on a non-privileged bus and are omitted.
|
|
30
|
+
# For a hardened system-service deployment, promote this unit to
|
|
31
|
+
# /etc/systemd/system/ and re-add those directives.
|
|
26
32
|
NoNewPrivileges=true
|
|
27
33
|
ProtectSystem=strict
|
|
28
34
|
ProtectHome=read-only
|
|
29
35
|
ReadWritePaths=%h/.local/share/getbased
|
|
30
36
|
PrivateTmp=true
|
|
31
|
-
ProtectKernelTunables=true
|
|
32
|
-
ProtectKernelModules=true
|
|
33
|
-
ProtectControlGroups=true
|
|
34
|
-
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
|
35
|
-
RestrictNamespaces=true
|
|
36
37
|
LockPersonality=true
|
|
37
|
-
MemoryDenyWriteExecute=false # ONNX Runtime needs W^X exception
|
|
38
|
-
RestrictRealtime=true
|
|
39
38
|
|
|
40
39
|
[Install]
|
|
41
40
|
WantedBy=default.target
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Regression tests for the bundled systemd unit files.
|
|
2
|
+
|
|
3
|
+
Found during the Hermes 0.4.0 clean-install smoke test: several hardening
|
|
4
|
+
directives (ProtectKernelTunables, RestrictNamespaces, etc.) require
|
|
5
|
+
CAP_SYS_ADMIN and fail with 218/CAPABILITIES under `systemctl --user`.
|
|
6
|
+
Inline `# comment` on a directive line also breaks parsing (systemd
|
|
7
|
+
appends the comment to the value).
|
|
8
|
+
|
|
9
|
+
These tests lock in both fixes by asserting on unit-file content."""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from getbased_agent_stack import units
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Capability-requiring directives that fail under `systemctl --user`.
|
|
18
|
+
# Any of these in a bundled unit would bring back the CAPABILITIES 218
|
|
19
|
+
# error on a user install.
|
|
20
|
+
FORBIDDEN_USER_MODE_DIRECTIVES = (
|
|
21
|
+
"ProtectKernelTunables",
|
|
22
|
+
"ProtectKernelModules",
|
|
23
|
+
"ProtectControlGroups",
|
|
24
|
+
"RestrictNamespaces",
|
|
25
|
+
"RestrictAddressFamilies",
|
|
26
|
+
"RestrictRealtime",
|
|
27
|
+
"MemoryDenyWriteExecute",
|
|
28
|
+
"SystemCallFilter",
|
|
29
|
+
"SystemCallArchitectures",
|
|
30
|
+
"CapabilityBoundingSet",
|
|
31
|
+
"AmbientCapabilities",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _directive_values(text: str) -> "list[tuple[str, str]]":
|
|
36
|
+
"""Return (key, value) pairs for every `KEY=VALUE` line that is not a
|
|
37
|
+
comment, preserving order. Section headers excluded."""
|
|
38
|
+
out: "list[tuple[str, str]]" = []
|
|
39
|
+
for line in text.splitlines():
|
|
40
|
+
stripped = line.strip()
|
|
41
|
+
if not stripped or stripped.startswith(("#", "[")):
|
|
42
|
+
continue
|
|
43
|
+
if "=" not in stripped:
|
|
44
|
+
continue
|
|
45
|
+
key, _, val = stripped.partition("=")
|
|
46
|
+
out.append((key.strip(), val))
|
|
47
|
+
return out
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.mark.parametrize("name,text", units.bundled_units())
|
|
51
|
+
def test_no_user_mode_forbidden_directives(name, text):
|
|
52
|
+
"""Capability-requiring directives must not appear in bundled units —
|
|
53
|
+
they're incompatible with `systemctl --user` and prevent first start."""
|
|
54
|
+
for key, _ in _directive_values(text):
|
|
55
|
+
assert key not in FORBIDDEN_USER_MODE_DIRECTIVES, (
|
|
56
|
+
f"{name} has {key}= which fails under systemctl --user "
|
|
57
|
+
f"(needs CAP_SYS_ADMIN, causes 218/CAPABILITIES)"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@pytest.mark.parametrize("name,text", units.bundled_units())
|
|
62
|
+
def test_no_inline_comments_on_directives(name, text):
|
|
63
|
+
"""systemd does not strip inline `# comments` from directive values.
|
|
64
|
+
A line like `MemoryDenyWriteExecute=false # needed for ONNX` parses
|
|
65
|
+
the full string (including '# needed for ONNX') as the value, which
|
|
66
|
+
breaks boolean directives and was a real Hermes install bug."""
|
|
67
|
+
for key, val in _directive_values(text):
|
|
68
|
+
# An '#' AFTER meaningful content on a directive line is an inline
|
|
69
|
+
# comment. Leading-# lines were filtered already by _directive_values.
|
|
70
|
+
assert "#" not in val, (
|
|
71
|
+
f"{name} directive {key}= has an inline comment; systemd won't "
|
|
72
|
+
f"strip it. Move the comment to its own line."
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@pytest.mark.parametrize("name,text", units.bundled_units())
|
|
77
|
+
def test_restart_always_not_on_failure(name, text):
|
|
78
|
+
"""Restart=always (not on-failure) so a clean SIGTERM triggers restart.
|
|
79
|
+
`on-failure` was the exact bug that kept lens-rag.service dead on Hermes
|
|
80
|
+
for 5 hours — don't regress that."""
|
|
81
|
+
restart_values = [val for key, val in _directive_values(text) if key == "Restart"]
|
|
82
|
+
assert restart_values, f"{name} has no Restart= directive"
|
|
83
|
+
assert restart_values[-1] == "always", (
|
|
84
|
+
f"{name} has Restart={restart_values[-1]}; must be `always` so clean "
|
|
85
|
+
f"SIGTERM still brings the service back."
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@pytest.mark.parametrize("name,text", units.bundled_units())
|
|
90
|
+
def test_reads_shared_env_file(name, text):
|
|
91
|
+
"""Every unit must source the shared env file via EnvironmentFile=
|
|
92
|
+
so the GETBASED_STACK_MANAGED flag + token + paths are available."""
|
|
93
|
+
values = [val for key, val in _directive_values(text) if key == "EnvironmentFile"]
|
|
94
|
+
assert any(
|
|
95
|
+
"getbased/env" in v for v in values
|
|
96
|
+
), f"{name} does not source %h/.config/getbased/env via EnvironmentFile="
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@pytest.mark.parametrize("name,text", units.bundled_units())
|
|
100
|
+
def test_sets_stack_managed_flag(name, text):
|
|
101
|
+
"""The opt-in flag must be set via Environment= so the Python loader
|
|
102
|
+
inside the binary picks up the shared file. Without this, services
|
|
103
|
+
run without the shared config and every user has to duplicate env
|
|
104
|
+
into the unit file manually."""
|
|
105
|
+
envs = [val for key, val in _directive_values(text) if key == "Environment"]
|
|
106
|
+
flag_set = any(e.startswith("GETBASED_STACK_MANAGED=1") for e in envs)
|
|
107
|
+
assert flag_set, (
|
|
108
|
+
f"{name} does not set GETBASED_STACK_MANAGED=1 via Environment="
|
|
109
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/src/getbased_agent_stack/env_file.py
RENAMED
|
File without changes
|
{getbased_agent_stack-0.4.0 → getbased_agent_stack-0.4.1}/src/getbased_agent_stack/mcp_configs.py
RENAMED
|
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
|