wraith-cli 1.3.0__tar.gz → 1.5.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.
- wraith_cli-1.5.0/.pre-commit-config.yaml +82 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/CHANGELOG.md +4 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/PKG-INFO +3 -30
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/README.md +0 -29
- wraith_cli-1.5.0/docs/usage.md +245 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/mkdocs.yml +0 -1
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/pyproject.toml +3 -1
- wraith_cli-1.5.0/src/wraith_cli/assets.py +10 -0
- wraith_cli-1.5.0/src/wraith_cli/config.py +134 -0
- wraith_cli-1.5.0/src/wraith_cli/main.py +524 -0
- wraith_cli-1.5.0/src/wraith_cli/providers.py +74 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/src/wraith_cli/qol.py +10 -4
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/src/wraith_cli/shield.py +48 -47
- wraith_cli-1.5.0/tests/conftest.py +39 -0
- wraith_cli-1.5.0/tests/test_cli.py +484 -0
- wraith_cli-1.5.0/tests/test_config.py +117 -0
- wraith_cli-1.5.0/tests/test_providers.py +126 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/tests/test_repo_make.py +8 -2
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/tests/test_shield.py +134 -42
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/uv.lock +135 -1
- wraith_cli-1.3.0/.pre-commit-config.yaml +0 -46
- wraith_cli-1.3.0/docs/usage.md +0 -80
- wraith_cli-1.3.0/src/wraith_cli/main.py +0 -339
- wraith_cli-1.3.0/tests/test_cli.py +0 -301
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/.cz.yaml +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/.env.example +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/.gitea/CODEOWNERS.md +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/.gitea/PULL_REQUEST_TEMPLATE.md +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/.gitea/workflows/pages.yml +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/.gitea/workflows/release.yml +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/.gitea/workflows/test.yml +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/.gitignore +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/.secrets.baseline +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/CONTRIBUTING.md +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/LICENSE +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/bin/build.sh +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/bin/run_tests.sh +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/bin/setup_venv.sh +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/docs/architecture.md +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/docs/index.md +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/docs/reference.md +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/docs/security.md +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/src/wraith_cli/__init__.py +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/src/wraith_cli/repo_make.py +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.5.0}/tests/test_qol.py +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
minimum_pre_commit_version: 4.0.0
|
|
2
|
+
default_stages: [pre-commit, pre-push]
|
|
3
|
+
|
|
4
|
+
repos:
|
|
5
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
6
|
+
rev: v6.0.0
|
|
7
|
+
hooks:
|
|
8
|
+
- id: trailing-whitespace
|
|
9
|
+
args: [--markdown-linebreak-ext=md]
|
|
10
|
+
exclude: ^src/wraith_cli/assets\.py$
|
|
11
|
+
- id: end-of-file-fixer
|
|
12
|
+
- id: check-yaml
|
|
13
|
+
- id: check-toml
|
|
14
|
+
- id: check-added-large-files
|
|
15
|
+
- id: check-case-conflict
|
|
16
|
+
|
|
17
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
18
|
+
rev: v0.15.9
|
|
19
|
+
hooks:
|
|
20
|
+
- id: ruff
|
|
21
|
+
args: ["--fix", "--exit-non-zero-on-fix"]
|
|
22
|
+
- id: ruff-format
|
|
23
|
+
|
|
24
|
+
- repo: https://github.com/codespell-project/codespell
|
|
25
|
+
rev: v2.4.2
|
|
26
|
+
hooks:
|
|
27
|
+
- id: codespell
|
|
28
|
+
types_or: [python, markdown, rst]
|
|
29
|
+
|
|
30
|
+
- repo: https://github.com/asottile/pyupgrade
|
|
31
|
+
rev: v3.21.2
|
|
32
|
+
hooks:
|
|
33
|
+
- id: pyupgrade
|
|
34
|
+
args: [--py312-plus]
|
|
35
|
+
|
|
36
|
+
- repo: https://github.com/shellcheck-py/shellcheck-py
|
|
37
|
+
rev: v0.11.0.1
|
|
38
|
+
hooks:
|
|
39
|
+
- id: shellcheck
|
|
40
|
+
args: ["--severity=warning"]
|
|
41
|
+
|
|
42
|
+
- repo: https://github.com/Yelp/detect-secrets
|
|
43
|
+
rev: v1.5.0
|
|
44
|
+
hooks:
|
|
45
|
+
- id: detect-secrets
|
|
46
|
+
args: ["--baseline", ".secrets.baseline"]
|
|
47
|
+
exclude: package-lock.json
|
|
48
|
+
|
|
49
|
+
- repo: local
|
|
50
|
+
hooks:
|
|
51
|
+
- id: generate-docs
|
|
52
|
+
name: Auto-generate CLI Documentation
|
|
53
|
+
entry: uv run typer src/wraith_cli/main.py utils docs --output docs/usage.md
|
|
54
|
+
language: system
|
|
55
|
+
pass_filenames: false
|
|
56
|
+
always_run: true
|
|
57
|
+
|
|
58
|
+
- id: no-raw-print
|
|
59
|
+
name: Ban raw print() in favor of typer.echo/rich
|
|
60
|
+
language: pygrep
|
|
61
|
+
entry: '(?<![\.a-zA-Z0-9_])print\('
|
|
62
|
+
types: [python]
|
|
63
|
+
|
|
64
|
+
- id: ty-check
|
|
65
|
+
name: ty check
|
|
66
|
+
entry: uv run ty check
|
|
67
|
+
language: system
|
|
68
|
+
types: [python]
|
|
69
|
+
pass_filenames: false
|
|
70
|
+
|
|
71
|
+
- id: pytest-coverage
|
|
72
|
+
name: pytest-coverage
|
|
73
|
+
entry: ./bin/run_tests.sh
|
|
74
|
+
language: system
|
|
75
|
+
pass_filenames: false
|
|
76
|
+
always_run: true
|
|
77
|
+
|
|
78
|
+
- id: commitizen
|
|
79
|
+
name: commitizen check
|
|
80
|
+
entry: uv run cz check --commit-msg-file
|
|
81
|
+
language: system
|
|
82
|
+
stages: [commit-msg]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wraith-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.0
|
|
4
4
|
Summary: Sovereign Command Centre for a Ghost Stack
|
|
5
5
|
Project-URL: Homepage, https://git.thomaspeoples.com/thomaspeoples/wraith-cli
|
|
6
6
|
Project-URL: Documentation, https://www.thomaspeoples.com/gitea-repos/wraith-cli/
|
|
@@ -32,7 +32,9 @@ Classifier: Programming Language :: Python :: 3
|
|
|
32
32
|
Classifier: Programming Language :: Python :: 3.12
|
|
33
33
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
34
34
|
Requires-Python: >=3.12
|
|
35
|
+
Requires-Dist: pydantic-settings>=2.0.0
|
|
35
36
|
Requires-Dist: python-dotenv
|
|
37
|
+
Requires-Dist: pyyaml>=6.0.1
|
|
36
38
|
Requires-Dist: requests
|
|
37
39
|
Requires-Dist: rich>=13.0.0
|
|
38
40
|
Requires-Dist: typer>=0.9.0
|
|
@@ -64,35 +66,6 @@ Description-Content-Type: text/markdown
|
|
|
64
66
|
high-level container orchestration and bare-metal reality, providing the "Ghost Factory"
|
|
65
67
|
for instant project scaffolding.
|
|
66
68
|
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
## 🏗️ The Ghost Factory (New)
|
|
70
|
-
|
|
71
|
-
The `spawn` command allows you to go from **Idea to Code** in under 5 seconds by
|
|
72
|
-
bleaching a template and provisioning a private Gitea repository automatically.
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
wraith spawn my-new-api
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
1. **🧬 Clones** your `GHOST_TEMPLATE_URL`.
|
|
79
|
-
2. **🧹 Bleaches** all previous git history.
|
|
80
|
-
3. **🌐 Provisions** a new private repo via the Gitea API.
|
|
81
|
-
4. **🚀 Pushes** the clean stack to your Sovereign remote.
|
|
82
|
-
|
|
83
|
-
---
|
|
84
|
-
|
|
85
|
-
## 🛠️ Operational Manual
|
|
86
|
-
|
|
87
|
-
| Command | Feature | Status |
|
|
88
|
-
|--------------------------|------------------------|---------------------------------------------|
|
|
89
|
-
| `wraith spawn <name>` | **The Ghost Factory** | 🏗️ Scaffolds new repos via Gitea API. |
|
|
90
|
-
| `wraith update` | **Global Update** | 🟢 PyPI-linked & `uv` powered. |
|
|
91
|
-
| `wraith ps` | **Rich Observability** | 🟢 Sovereign Dark styling for Docker. |
|
|
92
|
-
| `wraith tail <svc>` | **Flexible Logging** | 🟢 Supports `--path` & Env Vars. |
|
|
93
|
-
| `wraith status` | **Heartbeat** | 🟢 Monitor OpenViking (Port 1933). |
|
|
94
|
-
| `wraith runner-reset` | **Runner Defence** | 🟢 CI/CD maintenance & registration wipe. |
|
|
95
|
-
| `wraith --version` | **Self-Identity** | 🟢 Eager callback for versioning. |
|
|
96
69
|
|
|
97
70
|
---
|
|
98
71
|
|
|
@@ -11,35 +11,6 @@
|
|
|
11
11
|
high-level container orchestration and bare-metal reality, providing the "Ghost Factory"
|
|
12
12
|
for instant project scaffolding.
|
|
13
13
|
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## 🏗️ The Ghost Factory (New)
|
|
17
|
-
|
|
18
|
-
The `spawn` command allows you to go from **Idea to Code** in under 5 seconds by
|
|
19
|
-
bleaching a template and provisioning a private Gitea repository automatically.
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
wraith spawn my-new-api
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
1. **🧬 Clones** your `GHOST_TEMPLATE_URL`.
|
|
26
|
-
2. **🧹 Bleaches** all previous git history.
|
|
27
|
-
3. **🌐 Provisions** a new private repo via the Gitea API.
|
|
28
|
-
4. **🚀 Pushes** the clean stack to your Sovereign remote.
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## 🛠️ Operational Manual
|
|
33
|
-
|
|
34
|
-
| Command | Feature | Status |
|
|
35
|
-
|--------------------------|------------------------|---------------------------------------------|
|
|
36
|
-
| `wraith spawn <name>` | **The Ghost Factory** | 🏗️ Scaffolds new repos via Gitea API. |
|
|
37
|
-
| `wraith update` | **Global Update** | 🟢 PyPI-linked & `uv` powered. |
|
|
38
|
-
| `wraith ps` | **Rich Observability** | 🟢 Sovereign Dark styling for Docker. |
|
|
39
|
-
| `wraith tail <svc>` | **Flexible Logging** | 🟢 Supports `--path` & Env Vars. |
|
|
40
|
-
| `wraith status` | **Heartbeat** | 🟢 Monitor OpenViking (Port 1933). |
|
|
41
|
-
| `wraith runner-reset` | **Runner Defence** | 🟢 CI/CD maintenance & registration wipe. |
|
|
42
|
-
| `wraith --version` | **Self-Identity** | 🟢 Eager callback for versioning. |
|
|
43
14
|
|
|
44
15
|
---
|
|
45
16
|
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# CLI
|
|
2
|
+
|
|
3
|
+
Wraith Sovereign CLI: Ghost Stack Orchestrator
|
|
4
|
+
|
|
5
|
+
**Usage**:
|
|
6
|
+
|
|
7
|
+
```console
|
|
8
|
+
$ [OPTIONS] COMMAND [ARGS]...
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Options**:
|
|
12
|
+
|
|
13
|
+
* `--version`: Show version and exit.
|
|
14
|
+
* `--install-completion`: Install completion for the current shell.
|
|
15
|
+
* `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
|
|
16
|
+
* `--help`: Show this message and exit.
|
|
17
|
+
|
|
18
|
+
**Commands**:
|
|
19
|
+
|
|
20
|
+
* `init`: Bootstrap a new Wraith environment.
|
|
21
|
+
* `status`: Check the heartbeat of the OpenViking Stack.
|
|
22
|
+
* `health`: Visualise health of all configured services.
|
|
23
|
+
* `update`: Sync Wraith with the latest PyPI release.
|
|
24
|
+
* `ps`: List running containers with Sovereign...
|
|
25
|
+
* `tail`: Stream live logs from a specific service.
|
|
26
|
+
* `runner-reset`: Repair hung or offline Gitea Action Runners.
|
|
27
|
+
* `spawn`: Scaffold a new repository instantly.
|
|
28
|
+
* `logs`: Unified hardware health and logging portal.
|
|
29
|
+
* `mesh`: Map the Tailscale Ghost Mesh network.
|
|
30
|
+
* `audit`: Execute a Sovereign security and...
|
|
31
|
+
|
|
32
|
+
## `init`
|
|
33
|
+
|
|
34
|
+
Bootstrap a new Wraith environment.
|
|
35
|
+
|
|
36
|
+
Interactive wizard to configure the stack name, API URLs,
|
|
37
|
+
and auto-probe for Docker and Tailscale presence.
|
|
38
|
+
|
|
39
|
+
**Usage**:
|
|
40
|
+
|
|
41
|
+
```console
|
|
42
|
+
$ init [OPTIONS]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Options**:
|
|
46
|
+
|
|
47
|
+
* `--help`: Show this message and exit.
|
|
48
|
+
|
|
49
|
+
## `status`
|
|
50
|
+
|
|
51
|
+
Check the heartbeat of the OpenViking Stack.
|
|
52
|
+
|
|
53
|
+
Polls the configured VIKING_BASE_URL health
|
|
54
|
+
endpoint to verify if the orchestrator API is responsive.
|
|
55
|
+
|
|
56
|
+
**Usage**:
|
|
57
|
+
|
|
58
|
+
```console
|
|
59
|
+
$ status [OPTIONS]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Options**:
|
|
63
|
+
|
|
64
|
+
* `--help`: Show this message and exit.
|
|
65
|
+
|
|
66
|
+
## `health`
|
|
67
|
+
|
|
68
|
+
Visualise health of all configured services.
|
|
69
|
+
|
|
70
|
+
Polls the health_url for each service defined in .wraith.yaml.
|
|
71
|
+
Automatically parses OpenAPI/Swagger specs to find /health endpoints.
|
|
72
|
+
|
|
73
|
+
**Usage**:
|
|
74
|
+
|
|
75
|
+
```console
|
|
76
|
+
$ health [OPTIONS]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Options**:
|
|
80
|
+
|
|
81
|
+
* `--help`: Show this message and exit.
|
|
82
|
+
|
|
83
|
+
## `update`
|
|
84
|
+
|
|
85
|
+
Sync Wraith with the latest PyPI release.
|
|
86
|
+
|
|
87
|
+
Compares local versioning against the remote registry and
|
|
88
|
+
automatically triggers an upgrade via 'uv tool' if a newer
|
|
89
|
+
version is detected.
|
|
90
|
+
|
|
91
|
+
**Usage**:
|
|
92
|
+
|
|
93
|
+
```console
|
|
94
|
+
$ update [OPTIONS]
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Options**:
|
|
98
|
+
|
|
99
|
+
* `--help`: Show this message and exit.
|
|
100
|
+
|
|
101
|
+
## `ps`
|
|
102
|
+
|
|
103
|
+
List running containers with Sovereign styling.
|
|
104
|
+
|
|
105
|
+
Wraps 'docker ps' to provide a high-contrast,
|
|
106
|
+
readable table summarising service names,
|
|
107
|
+
container status, and source images.
|
|
108
|
+
|
|
109
|
+
**Usage**:
|
|
110
|
+
|
|
111
|
+
```console
|
|
112
|
+
$ ps [OPTIONS]
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Options**:
|
|
116
|
+
|
|
117
|
+
* `-a, --all`: Show all containers
|
|
118
|
+
* `--help`: Show this message and exit.
|
|
119
|
+
|
|
120
|
+
## `tail`
|
|
121
|
+
|
|
122
|
+
Stream live logs from a specific service.
|
|
123
|
+
|
|
124
|
+
Connects to a running container to output real-time logs.
|
|
125
|
+
Resolves Compose paths via CLI flags,
|
|
126
|
+
environment variables, or local directory discovery.
|
|
127
|
+
|
|
128
|
+
Priority:
|
|
129
|
+
1. Passed flag --path
|
|
130
|
+
2. Configured compose_path
|
|
131
|
+
3. Discovered via search_roots
|
|
132
|
+
4. Current Directory
|
|
133
|
+
|
|
134
|
+
**Usage**:
|
|
135
|
+
|
|
136
|
+
```console
|
|
137
|
+
$ tail [OPTIONS] SERVICE
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Arguments**:
|
|
141
|
+
|
|
142
|
+
* `SERVICE`: Service name (e.g., ollama, gitea) [required]
|
|
143
|
+
|
|
144
|
+
**Options**:
|
|
145
|
+
|
|
146
|
+
* `-p, --path PATH`: Path to directory with docker-compose.yml.
|
|
147
|
+
* `--help`: Show this message and exit.
|
|
148
|
+
|
|
149
|
+
## `runner-reset`
|
|
150
|
+
|
|
151
|
+
Repair hung or offline Gitea Action Runners.
|
|
152
|
+
|
|
153
|
+
Performs a nuclear reset: stops the container,
|
|
154
|
+
purges the local registration data, and restarts
|
|
155
|
+
the service to force a fresh handshake with Gitea.
|
|
156
|
+
|
|
157
|
+
Requires GITEA_COMPOSE_PATH as an envelope variable
|
|
158
|
+
|
|
159
|
+
**Usage**:
|
|
160
|
+
|
|
161
|
+
```console
|
|
162
|
+
$ runner-reset [OPTIONS]
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Options**:
|
|
166
|
+
|
|
167
|
+
* `--help`: Show this message and exit.
|
|
168
|
+
|
|
169
|
+
## `spawn`
|
|
170
|
+
|
|
171
|
+
Scaffold a new repository instantly.
|
|
172
|
+
|
|
173
|
+
Atomic scaffolding for new Ghost Stack repositories.
|
|
174
|
+
Clones a template, bleaches git history,
|
|
175
|
+
provisions a remote repository via API, and
|
|
176
|
+
pushes the initial commit in one sequence.
|
|
177
|
+
|
|
178
|
+
**Usage**:
|
|
179
|
+
|
|
180
|
+
```console
|
|
181
|
+
$ spawn [OPTIONS] REPO_NAME
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Arguments**:
|
|
185
|
+
|
|
186
|
+
* `REPO_NAME`: Name of the new Ghost Stack repository [required]
|
|
187
|
+
|
|
188
|
+
**Options**:
|
|
189
|
+
|
|
190
|
+
* `--help`: Show this message and exit.
|
|
191
|
+
|
|
192
|
+
## `logs`
|
|
193
|
+
|
|
194
|
+
Unified hardware health and logging portal.
|
|
195
|
+
|
|
196
|
+
Default behavior provides log-tailing instructions.
|
|
197
|
+
Using the --health flag triggers a deep-dive query
|
|
198
|
+
into the Scrutiny API for SMART drive data and disk temps.
|
|
199
|
+
|
|
200
|
+
**Usage**:
|
|
201
|
+
|
|
202
|
+
```console
|
|
203
|
+
$ logs [OPTIONS]
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Options**:
|
|
207
|
+
|
|
208
|
+
* `--health`: Pull SMART data summary from Scrutiny API
|
|
209
|
+
* `--help`: Show this message and exit.
|
|
210
|
+
|
|
211
|
+
## `mesh`
|
|
212
|
+
|
|
213
|
+
Map the Tailscale Ghost Mesh network.
|
|
214
|
+
|
|
215
|
+
Interrogates the Tailscale daemon (local or containerised)
|
|
216
|
+
to return a list of active peers, their internal IPs, and
|
|
217
|
+
current online/offline status.
|
|
218
|
+
|
|
219
|
+
**Usage**:
|
|
220
|
+
|
|
221
|
+
```console
|
|
222
|
+
$ mesh [OPTIONS]
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Options**:
|
|
226
|
+
|
|
227
|
+
* `--help`: Show this message and exit.
|
|
228
|
+
|
|
229
|
+
## `audit`
|
|
230
|
+
|
|
231
|
+
Execute a Sovereign security and compliance scan.
|
|
232
|
+
|
|
233
|
+
Audits the running stack for common vulnerabilities, including
|
|
234
|
+
containers running with root privileges and exposed ports
|
|
235
|
+
that deviate from the Registry Spec.
|
|
236
|
+
|
|
237
|
+
**Usage**:
|
|
238
|
+
|
|
239
|
+
```console
|
|
240
|
+
$ audit [OPTIONS]
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Options**:
|
|
244
|
+
|
|
245
|
+
* `--help`: Show this message and exit.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "wraith-cli"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.5.0"
|
|
8
8
|
description = "Sovereign Command Centre for a Ghost Stack"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -27,6 +27,8 @@ dependencies = [
|
|
|
27
27
|
"python-dotenv",
|
|
28
28
|
"rich>=13.0.0",
|
|
29
29
|
"typer>=0.9.0",
|
|
30
|
+
"pydantic-settings>=2.0.0",
|
|
31
|
+
"pyyaml>=6.0.1",
|
|
30
32
|
]
|
|
31
33
|
|
|
32
34
|
[project.urls]
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from functools import lru_cache
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
from pydantic import AliasChoices, BaseModel, Field, HttpUrl
|
|
7
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ServiceModel(BaseModel):
|
|
11
|
+
path: Path
|
|
12
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
13
|
+
health_url: HttpUrl | None = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TemplateModel(BaseModel):
|
|
17
|
+
url: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class WraithSettings(BaseSettings):
|
|
21
|
+
stack_name: str = "Ghost Stack"
|
|
22
|
+
viking_api: HttpUrl | None = None
|
|
23
|
+
search_roots: list[Path] = Field(default_factory=list)
|
|
24
|
+
services: dict[str, ServiceModel] = Field(default_factory=dict)
|
|
25
|
+
docker_enabled: bool = False
|
|
26
|
+
tailscale_enabled: bool = False
|
|
27
|
+
|
|
28
|
+
# Provider Settings
|
|
29
|
+
forge_api_url: str | None = None
|
|
30
|
+
forge_token: str | None = None
|
|
31
|
+
forge_type: str = "gitea"
|
|
32
|
+
templates: dict[str, TemplateModel] = Field(default_factory=dict)
|
|
33
|
+
|
|
34
|
+
# Legacy .env mappings
|
|
35
|
+
gitea_api_url: str | None = Field(
|
|
36
|
+
default=None,
|
|
37
|
+
validation_alias=AliasChoices("WRAITH_GITEA_API_URL", "GITEA_API_URL"),
|
|
38
|
+
)
|
|
39
|
+
gitea_token: str | None = Field(
|
|
40
|
+
default=None,
|
|
41
|
+
validation_alias=AliasChoices("WRAITH_GITEA_TOKEN", "GITEA_TOKEN"),
|
|
42
|
+
)
|
|
43
|
+
gitea_template_url: str | None = Field(
|
|
44
|
+
default=None,
|
|
45
|
+
validation_alias=AliasChoices(
|
|
46
|
+
"WRAITH_GITEA_TEMPLATE_URL", "GITEA_TEMPLATE_URL"
|
|
47
|
+
),
|
|
48
|
+
)
|
|
49
|
+
gitea_compose_path: Path | None = Field(
|
|
50
|
+
default=None,
|
|
51
|
+
validation_alias=AliasChoices(
|
|
52
|
+
"WRAITH_GITEA_COMPOSE_PATH", "GITEA_COMPOSE_PATH"
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
compose_path: Path | None = Field(
|
|
56
|
+
default=None,
|
|
57
|
+
validation_alias=AliasChoices("WRAITH_COMPOSE_PATH", "COMPOSE_PATH"),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Ingests .env variables as fallback, prioritizing WRAITH_ prefix
|
|
61
|
+
model_config = SettingsConfigDict(
|
|
62
|
+
env_prefix="WRAITH_",
|
|
63
|
+
env_file=".env",
|
|
64
|
+
env_file_encoding="utf-8",
|
|
65
|
+
extra="ignore",
|
|
66
|
+
populate_by_name=True,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@lru_cache
|
|
71
|
+
def get_settings() -> WraithSettings:
|
|
72
|
+
"""
|
|
73
|
+
Load settings with priority:
|
|
74
|
+
1. Local .wraith.yaml (CWD)
|
|
75
|
+
2. User ~/.config/wraith/config.yaml
|
|
76
|
+
3. Environment variables (WRAITH_ prefix & .env fallback)
|
|
77
|
+
"""
|
|
78
|
+
yaml_data: dict[str, Any] = {}
|
|
79
|
+
|
|
80
|
+
# 2. User-level config
|
|
81
|
+
user_config = Path.home() / ".config" / "wraith" / "config.yaml"
|
|
82
|
+
if user_config.exists(): # pragma: no cover
|
|
83
|
+
with open(user_config) as f:
|
|
84
|
+
parsed = yaml.safe_load(f)
|
|
85
|
+
if isinstance(parsed, dict):
|
|
86
|
+
yaml_data.update(parsed)
|
|
87
|
+
|
|
88
|
+
# 1. Local config (overrides user-level)
|
|
89
|
+
local_config = Path.cwd() / ".wraith.yaml"
|
|
90
|
+
if local_config.exists(): # pragma: no cover
|
|
91
|
+
with open(local_config) as f:
|
|
92
|
+
parsed = yaml.safe_load(f)
|
|
93
|
+
if isinstance(parsed, dict):
|
|
94
|
+
yaml_data.update(parsed)
|
|
95
|
+
|
|
96
|
+
# Pydantic BaseSettings kwargs override environment variables.
|
|
97
|
+
return WraithSettings(**yaml_data)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def discover_service(
|
|
101
|
+
service_name: str, search_roots: list[Path]
|
|
102
|
+
) -> Path | None: # pragma: no cover
|
|
103
|
+
"""
|
|
104
|
+
Recursively search for docker-compose.yml files in search_roots.
|
|
105
|
+
Matches if the directory name or container_name equals the service_name.
|
|
106
|
+
"""
|
|
107
|
+
for root in search_roots:
|
|
108
|
+
if not root.exists() or not root.is_dir():
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
for compose_file in root.rglob("docker-compose.yml"):
|
|
112
|
+
# Match by directory name
|
|
113
|
+
if compose_file.parent.name == service_name:
|
|
114
|
+
return compose_file
|
|
115
|
+
|
|
116
|
+
# Match by container_name or service key in yaml
|
|
117
|
+
try:
|
|
118
|
+
with open(compose_file) as f:
|
|
119
|
+
compose_data = yaml.safe_load(f)
|
|
120
|
+
if compose_data and isinstance(compose_data, dict):
|
|
121
|
+
services = compose_data.get("services", {})
|
|
122
|
+
for svc_key, svc_def in services.items():
|
|
123
|
+
if svc_key == service_name:
|
|
124
|
+
return compose_file
|
|
125
|
+
if (
|
|
126
|
+
isinstance(svc_def, dict)
|
|
127
|
+
and svc_def.get("container_name")
|
|
128
|
+
== service_name
|
|
129
|
+
):
|
|
130
|
+
return compose_file
|
|
131
|
+
except Exception:
|
|
132
|
+
pass # Ignore unparsable files during discovery
|
|
133
|
+
|
|
134
|
+
return None
|