storm-pulse-agent 0.1.6__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 (78) hide show
  1. storm_pulse_agent-0.1.6/LICENSE +21 -0
  2. storm_pulse_agent-0.1.6/PKG-INFO +17 -0
  3. storm_pulse_agent-0.1.6/README.md +107 -0
  4. storm_pulse_agent-0.1.6/pyproject.toml +37 -0
  5. storm_pulse_agent-0.1.6/setup.cfg +4 -0
  6. storm_pulse_agent-0.1.6/storm_pulse_agent.egg-info/PKG-INFO +17 -0
  7. storm_pulse_agent-0.1.6/storm_pulse_agent.egg-info/SOURCES.txt +76 -0
  8. storm_pulse_agent-0.1.6/storm_pulse_agent.egg-info/dependency_links.txt +1 -0
  9. storm_pulse_agent-0.1.6/storm_pulse_agent.egg-info/entry_points.txt +2 -0
  10. storm_pulse_agent-0.1.6/storm_pulse_agent.egg-info/requires.txt +10 -0
  11. storm_pulse_agent-0.1.6/storm_pulse_agent.egg-info/top_level.txt +1 -0
  12. storm_pulse_agent-0.1.6/stormpulse/__init__.py +5 -0
  13. storm_pulse_agent-0.1.6/stormpulse/__main__.py +6 -0
  14. storm_pulse_agent-0.1.6/stormpulse/agent.py +811 -0
  15. storm_pulse_agent-0.1.6/stormpulse/auth.py +220 -0
  16. storm_pulse_agent-0.1.6/stormpulse/caddy/__init__.py +11 -0
  17. storm_pulse_agent-0.1.6/stormpulse/caddy/commands.py +65 -0
  18. storm_pulse_agent-0.1.6/stormpulse/caddy/init.py +291 -0
  19. storm_pulse_agent-0.1.6/stormpulse/caddy/sync.py +297 -0
  20. storm_pulse_agent-0.1.6/stormpulse/cli/__init__.py +160 -0
  21. storm_pulse_agent-0.1.6/stormpulse/cli/caddy.py +55 -0
  22. storm_pulse_agent-0.1.6/stormpulse/cli/enroll.py +60 -0
  23. storm_pulse_agent-0.1.6/stormpulse/cli/garage.py +55 -0
  24. storm_pulse_agent-0.1.6/stormpulse/cli/init.py +25 -0
  25. storm_pulse_agent-0.1.6/stormpulse/cli/log.py +40 -0
  26. storm_pulse_agent-0.1.6/stormpulse/cli/run.py +83 -0
  27. storm_pulse_agent-0.1.6/stormpulse/cli/status.py +13 -0
  28. storm_pulse_agent-0.1.6/stormpulse/commands/__init__.py +26 -0
  29. storm_pulse_agent-0.1.6/stormpulse/commands/deploy.py +36 -0
  30. storm_pulse_agent-0.1.6/stormpulse/commands/jobs.py +255 -0
  31. storm_pulse_agent-0.1.6/stormpulse/commands/registry.py +256 -0
  32. storm_pulse_agent-0.1.6/stormpulse/config.py +651 -0
  33. storm_pulse_agent-0.1.6/stormpulse/enroll.py +262 -0
  34. storm_pulse_agent-0.1.6/stormpulse/garage/__init__.py +21 -0
  35. storm_pulse_agent-0.1.6/stormpulse/garage/clear_bucket.py +229 -0
  36. storm_pulse_agent-0.1.6/stormpulse/garage/commands.py +637 -0
  37. storm_pulse_agent-0.1.6/stormpulse/garage/delete_provisioned_bucket.py +445 -0
  38. storm_pulse_agent-0.1.6/stormpulse/garage/discover.py +39 -0
  39. storm_pulse_agent-0.1.6/stormpulse/garage/init.py +367 -0
  40. storm_pulse_agent-0.1.6/stormpulse/garage/parse.py +566 -0
  41. storm_pulse_agent-0.1.6/stormpulse/garage/provision_additional_key.py +345 -0
  42. storm_pulse_agent-0.1.6/stormpulse/garage/provision_bucket.py +537 -0
  43. storm_pulse_agent-0.1.6/stormpulse/garage/rotate_key.py +376 -0
  44. storm_pulse_agent-0.1.6/stormpulse/garage/runner.py +47 -0
  45. storm_pulse_agent-0.1.6/stormpulse/garage/s3.py +464 -0
  46. storm_pulse_agent-0.1.6/stormpulse/garage/set_cors.py +184 -0
  47. storm_pulse_agent-0.1.6/stormpulse/garage/state.py +180 -0
  48. storm_pulse_agent-0.1.6/stormpulse/garage/walk_bucket_stats.py +188 -0
  49. storm_pulse_agent-0.1.6/stormpulse/init/__init__.py +83 -0
  50. storm_pulse_agent-0.1.6/stormpulse/init/checks.py +85 -0
  51. storm_pulse_agent-0.1.6/stormpulse/init/compose.py +93 -0
  52. storm_pulse_agent-0.1.6/stormpulse/init/files.py +54 -0
  53. storm_pulse_agent-0.1.6/stormpulse/init/generate.py +114 -0
  54. storm_pulse_agent-0.1.6/stormpulse/init/orchestrator.py +103 -0
  55. storm_pulse_agent-0.1.6/stormpulse/init/prompts.py +138 -0
  56. storm_pulse_agent-0.1.6/stormpulse/init/registry.py +32 -0
  57. storm_pulse_agent-0.1.6/stormpulse/init/system.py +150 -0
  58. storm_pulse_agent-0.1.6/stormpulse/logging/__init__.py +25 -0
  59. storm_pulse_agent-0.1.6/stormpulse/logging/init.py +262 -0
  60. storm_pulse_agent-0.1.6/stormpulse/logging/parsers.py +321 -0
  61. storm_pulse_agent-0.1.6/stormpulse/logging/positions.py +118 -0
  62. storm_pulse_agent-0.1.6/stormpulse/logging/shipper.py +96 -0
  63. storm_pulse_agent-0.1.6/stormpulse/logging/tailer.py +457 -0
  64. storm_pulse_agent-0.1.6/stormpulse/logging/writer.py +101 -0
  65. storm_pulse_agent-0.1.6/stormpulse/metrics.py +124 -0
  66. storm_pulse_agent-0.1.6/stormpulse/protocol.py +408 -0
  67. storm_pulse_agent-0.1.6/stormpulse/status.py +142 -0
  68. storm_pulse_agent-0.1.6/tests/test_agent.py +1174 -0
  69. storm_pulse_agent-0.1.6/tests/test_auth.py +543 -0
  70. storm_pulse_agent-0.1.6/tests/test_commands.py +595 -0
  71. storm_pulse_agent-0.1.6/tests/test_config.py +977 -0
  72. storm_pulse_agent-0.1.6/tests/test_enroll.py +344 -0
  73. storm_pulse_agent-0.1.6/tests/test_init.py +1088 -0
  74. storm_pulse_agent-0.1.6/tests/test_integration.py +580 -0
  75. storm_pulse_agent-0.1.6/tests/test_jobs.py +342 -0
  76. storm_pulse_agent-0.1.6/tests/test_metrics.py +299 -0
  77. storm_pulse_agent-0.1.6/tests/test_protocol.py +890 -0
  78. storm_pulse_agent-0.1.6/tests/test_status.py +343 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mathew Storm
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: storm-pulse-agent
3
+ Version: 0.1.6
4
+ Summary: Secure server management agent for Storm Developments infrastructure
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.12
7
+ License-File: LICENSE
8
+ Requires-Dist: websockets>=14.0
9
+ Requires-Dist: psutil>=6.0
10
+ Requires-Dist: cryptography>=48.0.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=8.0; extra == "dev"
13
+ Requires-Dist: pytest-asyncio>=1.0; extra == "dev"
14
+ Requires-Dist: mypy>=1.13; extra == "dev"
15
+ Requires-Dist: types-psutil>=6.0; extra == "dev"
16
+ Requires-Dist: import-linter>=2.0; extra == "dev"
17
+ Dynamic: license-file
@@ -0,0 +1,107 @@
1
+ # Storm Pulse Agent
2
+
3
+ [![CI](https://git.stormdevelopments.ca/official-public/storm-pulse/actions/workflows/test.yml/badge.svg)](https://git.stormdevelopments.ca/official-public/storm-pulse/actions)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
+ [![Typed: mypy strict](https://img.shields.io/badge/mypy-strict-blue.svg)](https://mypy-lang.org/)
6
+
7
+ Secure server management agent for [Storm Developments](https://stormdevelopments.ca). Connects outbound to a Django dashboard over WebSocket with mTLS, pushes system metrics, and executes whitelisted deploy commands. Zero listening ports.
8
+
9
+ ## How It Works
10
+
11
+ 1. Agent connects **outbound** to the dashboard. Nginx terminates mTLS.
12
+ 2. Sends a `register` message (including its available commands list), then pushes metrics every 15s (CPU, memory, disk, load, containers).
13
+ 3. Dashboard sends HMAC-signed commands. Agent verifies signature, nonce, and expiry before executing.
14
+ 4. Commands run via `subprocess.run(shell=False)` against a strict whitelist. Custom commands can be added via config with optional overridable parameters (regex-validated). No shell injection possible.
15
+
16
+ Read the [Protocol Specification](https://git.stormdevelopments.ca/official-public/storm-pulse/wiki/Protocol-Specification) for exact information.
17
+
18
+ ## Security
19
+
20
+ Five layers, each independent:
21
+
22
+ - **Network** -- No inbound ports. Agent initiates all connections.
23
+ - **Transport** -- mTLS with per-agent certs from a private CA.
24
+ - **Application** -- HMAC-SHA256 + nonce + expiry on every command.
25
+ - **Execution** -- Whitelisted commands only. Absolute paths. `shell=False`. Config placeholders from local config only; runtime params are regex-validated.
26
+ - **OS** -- Dedicated user. Systemd sandboxing.
27
+
28
+ See the [Security Architecture](https://git.stormdevelopments.ca/official-public/storm-pulse/wiki/Security-Architecture) wiki page for the full design.
29
+
30
+ ## Setup
31
+
32
+ Requires Python 3.12+. Three runtime deps: `websockets`, `psutil`, `cryptography`.
33
+
34
+ Install from source:
35
+
36
+ ```bash
37
+ pip install git+https://git.stormdevelopments.ca/official-public/storm-pulse.git
38
+ ```
39
+
40
+ For full setup instructions (system user, permissions, systemd, firewall), see the [Setup Guide](https://git.stormdevelopments.ca/official-public/storm-pulse/wiki/Setup-Guide).
41
+
42
+ ## CLI
43
+
44
+ ```
45
+ stormpulse enroll ENDPOINT AGENT_ID TOKEN [--creds-dir DIR] [--force]
46
+ stormpulse init [--creds-dir DIR] [--force]
47
+ stormpulse run [CONFIG]
48
+ stormpulse status [CONFIG]
49
+ stormpulse garage init [--config PATH] [--garage-config PATH] [--force]
50
+ stormpulse logging init [--config PATH]
51
+ stormpulse --version
52
+ ```
53
+
54
+ **enroll** -- One-time enrollment. Generates an EC P-256 keypair, sends a CSR to the dashboard, writes the signed cert + CA cert + HMAC key to `/etc/stormpulse/`. The private key never leaves the machine.
55
+
56
+ **init** -- Interactive setup wizard. Generates config, creates systemd service, sets permissions. Run after enrollment. Auto-detects Garage installations and running Docker containers and offers to enable integration / log shipping.
57
+
58
+ **run** -- Starts the agent. Connects to the dashboard, sends heartbeats and metrics, executes commands. Reconnects automatically with exponential backoff.
59
+
60
+ **status** -- Local inspection. Shows version, agent ID, config path, dashboard URL, certificate expiry, nonce DB entry count, and whether the agent process is running. No network required.
61
+
62
+ **garage init** -- Detects a Garage S3 node and appends a `[garage]` section to an existing `stormpulse.toml`. Auto-detects container name from docker-compose.yml. Use `--force` to overwrite an existing `[garage]` section.
63
+
64
+ **logging init** -- Detects running Docker containers and appends `[[log_groups]]` blocks for each, using `source_type = "docker"` and the `docker_raw` parser. Skips containers already present in the config. See [Log Shipping](https://git.stormdevelopments.ca/official-public/storm-pulse/wiki/Logging) for details.
65
+
66
+ ## Configuration
67
+
68
+ Run `stormpulse init` to generate a config interactively - see the [Setup Guide](https://git.stormdevelopments.ca/official-public/storm-pulse/wiki/Setup-Guide). Key settings:
69
+
70
+ | Section | Field | Description |
71
+ |---------|-------|-------------|
72
+ | `agent` | `id` | Unique identifier for this server |
73
+ | `agent` | `pulse_token` | UUID from the Server record in the dashboard |
74
+ | `agent` | `disabled_commands` | List of command names to remove from the registry (optional) |
75
+ | `dashboard` | `url` | WebSocket URL (`wss://...`) |
76
+ | `project` | `project_dir` | Absolute path to the deployed project |
77
+ | `project` | `compose_file` | Absolute path to docker-compose.yml |
78
+ | `project` | `env_file` | Absolute path to `.env` file (optional, passed as `--env-file` to docker compose) |
79
+ | `commands.*` | | Custom commands (optional, see example config) |
80
+ | `garage` | `enabled` | Enable Garage S3 integration (optional, default: absent) |
81
+ | `garage` | `container_name` | Docker container name for Garage (e.g. `garaged`) |
82
+ | `garage` | `config_path` | Path to Garage config file |
83
+ | `garage` | `state_push_interval_seconds` | How often to refresh Garage state (default: 30) |
84
+
85
+ ## Documentation
86
+
87
+ - [Setup Guide](https://git.stormdevelopments.ca/official-public/storm-pulse/wiki/Setup-Guide) -- Full install, enrollment, systemd, permissions
88
+ - [Customize Commands](https://git.stormdevelopments.ca/official-public/storm-pulse/wiki/Customize--Commands) -- How to disable existing commands, or whitelist new commands
89
+ - [Log Shipping](https://git.stormdevelopments.ca/official-public/storm-pulse/wiki/Logging) -- Tailing container and file logs to the dashboard
90
+ - [Garage Integration](https://git.stormdevelopments.ca/official-public/storm-pulse/wiki/Garage-Integration) -- Garage S3 node management
91
+ - [Protocol Specification](https://git.stormdevelopments.ca/official-public/storm-pulse/wiki/Protocol-Specification) -- Message formats, envelope structure, versioning
92
+ - [Security Architecture](https://git.stormdevelopments.ca/official-public/storm-pulse/wiki/Security-Architecture) -- Threat model, five security layers
93
+
94
+ ## Develop
95
+
96
+ ```bash
97
+ git clone https://git.stormdevelopments.ca/official-public/storm-pulse.git && cd storm-pulse
98
+ python3 -m venv .venv && source .venv/bin/activate
99
+ pip install -e ".[dev]"
100
+ pytest
101
+ mypy . # strict
102
+ make fitness # architecture + security invariants
103
+ ```
104
+
105
+ ## License
106
+
107
+ MIT - see [LICENSE](LICENSE).
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "storm-pulse-agent"
7
+ version = "0.1.6"
8
+ description = "Secure server management agent for Storm Developments infrastructure"
9
+ requires-python = ">=3.12"
10
+ license = "MIT"
11
+ dependencies = [
12
+ "websockets>=14.0",
13
+ "psutil>=6.0",
14
+ "cryptography>=48.0.0",
15
+ ]
16
+
17
+ [project.optional-dependencies]
18
+ dev = ["pytest>=8.0", "pytest-asyncio>=1.0", "mypy>=1.13", "types-psutil>=6.0", "import-linter>=2.0"]
19
+
20
+ [project.scripts]
21
+ stormpulse = "stormpulse.cli:main"
22
+
23
+ [tool.pytest.ini_options]
24
+ testpaths = ["tests"]
25
+ pythonpath = ["."]
26
+
27
+ [tool.mypy]
28
+ strict = true
29
+ python_version = "3.12"
30
+
31
+ [[tool.mypy.overrides]]
32
+ module = "tests.*"
33
+ strict = false
34
+ check_untyped_defs = true
35
+
36
+ [tool.setuptools.packages.find]
37
+ include = ["stormpulse*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: storm-pulse-agent
3
+ Version: 0.1.6
4
+ Summary: Secure server management agent for Storm Developments infrastructure
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.12
7
+ License-File: LICENSE
8
+ Requires-Dist: websockets>=14.0
9
+ Requires-Dist: psutil>=6.0
10
+ Requires-Dist: cryptography>=48.0.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=8.0; extra == "dev"
13
+ Requires-Dist: pytest-asyncio>=1.0; extra == "dev"
14
+ Requires-Dist: mypy>=1.13; extra == "dev"
15
+ Requires-Dist: types-psutil>=6.0; extra == "dev"
16
+ Requires-Dist: import-linter>=2.0; extra == "dev"
17
+ Dynamic: license-file
@@ -0,0 +1,76 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ storm_pulse_agent.egg-info/PKG-INFO
5
+ storm_pulse_agent.egg-info/SOURCES.txt
6
+ storm_pulse_agent.egg-info/dependency_links.txt
7
+ storm_pulse_agent.egg-info/entry_points.txt
8
+ storm_pulse_agent.egg-info/requires.txt
9
+ storm_pulse_agent.egg-info/top_level.txt
10
+ stormpulse/__init__.py
11
+ stormpulse/__main__.py
12
+ stormpulse/agent.py
13
+ stormpulse/auth.py
14
+ stormpulse/config.py
15
+ stormpulse/enroll.py
16
+ stormpulse/metrics.py
17
+ stormpulse/protocol.py
18
+ stormpulse/status.py
19
+ stormpulse/caddy/__init__.py
20
+ stormpulse/caddy/commands.py
21
+ stormpulse/caddy/init.py
22
+ stormpulse/caddy/sync.py
23
+ stormpulse/cli/__init__.py
24
+ stormpulse/cli/caddy.py
25
+ stormpulse/cli/enroll.py
26
+ stormpulse/cli/garage.py
27
+ stormpulse/cli/init.py
28
+ stormpulse/cli/log.py
29
+ stormpulse/cli/run.py
30
+ stormpulse/cli/status.py
31
+ stormpulse/commands/__init__.py
32
+ stormpulse/commands/deploy.py
33
+ stormpulse/commands/jobs.py
34
+ stormpulse/commands/registry.py
35
+ stormpulse/garage/__init__.py
36
+ stormpulse/garage/clear_bucket.py
37
+ stormpulse/garage/commands.py
38
+ stormpulse/garage/delete_provisioned_bucket.py
39
+ stormpulse/garage/discover.py
40
+ stormpulse/garage/init.py
41
+ stormpulse/garage/parse.py
42
+ stormpulse/garage/provision_additional_key.py
43
+ stormpulse/garage/provision_bucket.py
44
+ stormpulse/garage/rotate_key.py
45
+ stormpulse/garage/runner.py
46
+ stormpulse/garage/s3.py
47
+ stormpulse/garage/set_cors.py
48
+ stormpulse/garage/state.py
49
+ stormpulse/garage/walk_bucket_stats.py
50
+ stormpulse/init/__init__.py
51
+ stormpulse/init/checks.py
52
+ stormpulse/init/compose.py
53
+ stormpulse/init/files.py
54
+ stormpulse/init/generate.py
55
+ stormpulse/init/orchestrator.py
56
+ stormpulse/init/prompts.py
57
+ stormpulse/init/registry.py
58
+ stormpulse/init/system.py
59
+ stormpulse/logging/__init__.py
60
+ stormpulse/logging/init.py
61
+ stormpulse/logging/parsers.py
62
+ stormpulse/logging/positions.py
63
+ stormpulse/logging/shipper.py
64
+ stormpulse/logging/tailer.py
65
+ stormpulse/logging/writer.py
66
+ tests/test_agent.py
67
+ tests/test_auth.py
68
+ tests/test_commands.py
69
+ tests/test_config.py
70
+ tests/test_enroll.py
71
+ tests/test_init.py
72
+ tests/test_integration.py
73
+ tests/test_jobs.py
74
+ tests/test_metrics.py
75
+ tests/test_protocol.py
76
+ tests/test_status.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ stormpulse = stormpulse.cli:main
@@ -0,0 +1,10 @@
1
+ websockets>=14.0
2
+ psutil>=6.0
3
+ cryptography>=48.0.0
4
+
5
+ [dev]
6
+ pytest>=8.0
7
+ pytest-asyncio>=1.0
8
+ mypy>=1.13
9
+ types-psutil>=6.0
10
+ import-linter>=2.0
@@ -0,0 +1,5 @@
1
+ """Storm Pulse Agent - secure server management over WebSocket."""
2
+
3
+ from importlib.metadata import version
4
+
5
+ __version__ = version("storm-pulse-agent")
@@ -0,0 +1,6 @@
1
+ """Allow running as ``python -m stormpulse``."""
2
+
3
+ from stormpulse.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()