wraith-cli 1.3.0__tar.gz → 1.4.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.4.0/.pre-commit-config.yaml +82 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/CHANGELOG.md +2 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/PKG-INFO +1 -30
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/README.md +0 -29
- wraith_cli-1.4.0/docs/usage.md +208 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/pyproject.toml +1 -1
- wraith_cli-1.4.0/src/wraith_cli/assets.py +10 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/src/wraith_cli/main.py +76 -27
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/src/wraith_cli/shield.py +49 -25
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/tests/test_cli.py +1 -1
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/tests/test_shield.py +108 -5
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/uv.lock +1 -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 → wraith_cli-1.4.0}/.cz.yaml +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/.env.example +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/.gitea/CODEOWNERS.md +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/.gitea/PULL_REQUEST_TEMPLATE.md +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/.gitea/workflows/pages.yml +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/.gitea/workflows/release.yml +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/.gitea/workflows/test.yml +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/.gitignore +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/.secrets.baseline +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/CONTRIBUTING.md +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/LICENSE +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/bin/build.sh +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/bin/run_tests.sh +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/bin/setup_venv.sh +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/docs/architecture.md +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/docs/index.md +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/docs/reference.md +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/docs/security.md +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/mkdocs.yml +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/src/wraith_cli/__init__.py +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/src/wraith_cli/qol.py +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/src/wraith_cli/repo_make.py +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/tests/test_qol.py +0 -0
- {wraith_cli-1.3.0 → wraith_cli-1.4.0}/tests/test_repo_make.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.4.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/
|
|
@@ -64,35 +64,6 @@ Description-Content-Type: text/markdown
|
|
|
64
64
|
high-level container orchestration and bare-metal reality, providing the "Ghost Factory"
|
|
65
65
|
for instant project scaffolding.
|
|
66
66
|
|
|
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
67
|
|
|
97
68
|
---
|
|
98
69
|
|
|
@@ -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,208 @@
|
|
|
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
|
+
* `status`: Check the heartbeat of the OpenViking Stack.
|
|
21
|
+
* `update`: Sync Wraith with the latest PyPI release.
|
|
22
|
+
* `ps`: List running containers with Sovereign...
|
|
23
|
+
* `tail`: Stream live logs from a specific service.
|
|
24
|
+
* `runner-reset`: Repair hung or offline Gitea Action Runners.
|
|
25
|
+
* `spawn`: 🏭 Scaffold a new repository instantly.
|
|
26
|
+
* `logs`: Unified hardware health and logging portal.
|
|
27
|
+
* `mesh`: Map the Tailscale Ghost Mesh network.
|
|
28
|
+
* `audit`: Execute a Sovereign security and...
|
|
29
|
+
|
|
30
|
+
## `status`
|
|
31
|
+
|
|
32
|
+
Check the heartbeat of the OpenViking Stack.
|
|
33
|
+
|
|
34
|
+
Polls the configured VIKING_BASE_URL health
|
|
35
|
+
nendpoint to verify if the orchestrator API is responsive.
|
|
36
|
+
|
|
37
|
+
**Usage**:
|
|
38
|
+
|
|
39
|
+
```console
|
|
40
|
+
$ status [OPTIONS]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Options**:
|
|
44
|
+
|
|
45
|
+
* `--help`: Show this message and exit.
|
|
46
|
+
|
|
47
|
+
## `update`
|
|
48
|
+
|
|
49
|
+
Sync Wraith with the latest PyPI release.
|
|
50
|
+
|
|
51
|
+
Compares local versioning against the remote registry and
|
|
52
|
+
automatically triggers an upgrade via 'uv tool' if a newer
|
|
53
|
+
version is detected.
|
|
54
|
+
|
|
55
|
+
**Usage**:
|
|
56
|
+
|
|
57
|
+
```console
|
|
58
|
+
$ update [OPTIONS]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Options**:
|
|
62
|
+
|
|
63
|
+
* `--help`: Show this message and exit.
|
|
64
|
+
|
|
65
|
+
## `ps`
|
|
66
|
+
|
|
67
|
+
List running containers with Sovereign styling.
|
|
68
|
+
|
|
69
|
+
Wraps 'docker ps' to provide a high-contrast,
|
|
70
|
+
readable table summarising service names,
|
|
71
|
+
container status, and source images.
|
|
72
|
+
|
|
73
|
+
**Usage**:
|
|
74
|
+
|
|
75
|
+
```console
|
|
76
|
+
$ ps [OPTIONS]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Options**:
|
|
80
|
+
|
|
81
|
+
* `-a, --all`: Show all containers
|
|
82
|
+
* `--help`: Show this message and exit.
|
|
83
|
+
|
|
84
|
+
## `tail`
|
|
85
|
+
|
|
86
|
+
Stream live logs from a specific service.
|
|
87
|
+
|
|
88
|
+
Connects to a running container to output real-time logs.
|
|
89
|
+
Resolves Compose paths via CLI flags,
|
|
90
|
+
environment variables, or local directory discovery.
|
|
91
|
+
|
|
92
|
+
Priority:
|
|
93
|
+
1. Passed flag --path
|
|
94
|
+
2. Env Var WRAITH_COMPOSE_PATH
|
|
95
|
+
3. Current Directory
|
|
96
|
+
|
|
97
|
+
**Usage**:
|
|
98
|
+
|
|
99
|
+
```console
|
|
100
|
+
$ tail [OPTIONS] SERVICE
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Arguments**:
|
|
104
|
+
|
|
105
|
+
* `SERVICE`: Service name (e.g., ollama, gitea) [required]
|
|
106
|
+
|
|
107
|
+
**Options**:
|
|
108
|
+
|
|
109
|
+
* `-p, --path PATH`: Path to directory with docker-compose.yml. [env var: WRAITH_COMPOSE_PATH]
|
|
110
|
+
* `--help`: Show this message and exit.
|
|
111
|
+
|
|
112
|
+
## `runner-reset`
|
|
113
|
+
|
|
114
|
+
Repair hung or offline Gitea Action Runners.
|
|
115
|
+
|
|
116
|
+
Performs a nuclear reset: stops the container,
|
|
117
|
+
purges the local registration data, and restarts
|
|
118
|
+
the service to force a fresh handshake with Gitea.
|
|
119
|
+
|
|
120
|
+
Requires GITEA_COMPOSE_PATH as an envelope variable
|
|
121
|
+
|
|
122
|
+
**Usage**:
|
|
123
|
+
|
|
124
|
+
```console
|
|
125
|
+
$ runner-reset [OPTIONS]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Options**:
|
|
129
|
+
|
|
130
|
+
* `--help`: Show this message and exit.
|
|
131
|
+
|
|
132
|
+
## `spawn`
|
|
133
|
+
|
|
134
|
+
🏭 Scaffold a new repository instantly.
|
|
135
|
+
|
|
136
|
+
Atomic scaffolding for new Ghost Stack repositories.
|
|
137
|
+
Clones a template, bleaches git history,
|
|
138
|
+
provisions a remote repository via API, and
|
|
139
|
+
pushes the initial commit in one sequence.
|
|
140
|
+
|
|
141
|
+
**Usage**:
|
|
142
|
+
|
|
143
|
+
```console
|
|
144
|
+
$ spawn [OPTIONS] REPO_NAME
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Arguments**:
|
|
148
|
+
|
|
149
|
+
* `REPO_NAME`: Name of the new Ghost Stack repository [required]
|
|
150
|
+
|
|
151
|
+
**Options**:
|
|
152
|
+
|
|
153
|
+
* `--help`: Show this message and exit.
|
|
154
|
+
|
|
155
|
+
## `logs`
|
|
156
|
+
|
|
157
|
+
Unified hardware health and logging portal.
|
|
158
|
+
|
|
159
|
+
Default behavior provides log-tailing instructions.
|
|
160
|
+
Using the --health flag triggers a deep-dive query
|
|
161
|
+
into the Scrutiny API for SMART drive data and disk temps.
|
|
162
|
+
|
|
163
|
+
**Usage**:
|
|
164
|
+
|
|
165
|
+
```console
|
|
166
|
+
$ logs [OPTIONS]
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Options**:
|
|
170
|
+
|
|
171
|
+
* `--health`: Pull SMART data summary from Scrutiny API
|
|
172
|
+
* `--help`: Show this message and exit.
|
|
173
|
+
|
|
174
|
+
## `mesh`
|
|
175
|
+
|
|
176
|
+
Map the Tailscale Ghost Mesh network.
|
|
177
|
+
|
|
178
|
+
Interrogates the Tailscale daemon (local or containerised)
|
|
179
|
+
to return a list of active peers, their internal IPs, and
|
|
180
|
+
current online/offline status.
|
|
181
|
+
|
|
182
|
+
**Usage**:
|
|
183
|
+
|
|
184
|
+
```console
|
|
185
|
+
$ mesh [OPTIONS]
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Options**:
|
|
189
|
+
|
|
190
|
+
* `--help`: Show this message and exit.
|
|
191
|
+
|
|
192
|
+
## `audit`
|
|
193
|
+
|
|
194
|
+
Execute a Sovereign security and compliance scan.
|
|
195
|
+
|
|
196
|
+
Audits the running stack for common vulnerabilities, including
|
|
197
|
+
containers running with root privileges and exposed ports
|
|
198
|
+
that deviate from the Registry Spec.
|
|
199
|
+
|
|
200
|
+
**Usage**:
|
|
201
|
+
|
|
202
|
+
```console
|
|
203
|
+
$ audit [OPTIONS]
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Options**:
|
|
207
|
+
|
|
208
|
+
* `--help`: Show this message and exit.
|
|
@@ -12,10 +12,10 @@ from rich.markdown import Markdown
|
|
|
12
12
|
from rich.panel import Panel
|
|
13
13
|
from rich.text import Text
|
|
14
14
|
|
|
15
|
-
from wraith_cli import qol, repo_make, shield
|
|
15
|
+
from wraith_cli import assets, qol, repo_make, shield
|
|
16
16
|
|
|
17
17
|
SOVEREIGN_ADVISORY = """
|
|
18
|
-
##
|
|
18
|
+
## Sovereign Security Advisory – Please Read
|
|
19
19
|
|
|
20
20
|
**Wraith-CLI** is your stack’s nervous system and has sharp edges.
|
|
21
21
|
The commands in this toolkit can wipe runner data (`runner-reset`),
|
|
@@ -50,7 +50,12 @@ GITEA_TEMPLATE_URL = os.getenv("GITEA_TEMPLATE_URL")
|
|
|
50
50
|
|
|
51
51
|
# --- Callbacks ---
|
|
52
52
|
def version_callback(value: bool):
|
|
53
|
-
"""
|
|
53
|
+
"""
|
|
54
|
+
Display the current Wraith-CLI version.
|
|
55
|
+
|
|
56
|
+
Checks the local metadata to return the
|
|
57
|
+
installed version string before exiting the application.
|
|
58
|
+
"""
|
|
54
59
|
if value:
|
|
55
60
|
v = get_local_version("wraith-cli")
|
|
56
61
|
typer.echo(f"Wraith CLI v{v}")
|
|
@@ -62,14 +67,7 @@ def print_wraith_welcome(): # pragma: no cover
|
|
|
62
67
|
console = qol.console
|
|
63
68
|
local_v = get_local_version("wraith-cli")
|
|
64
69
|
|
|
65
|
-
|
|
66
|
-
▖ ▖▄▖▄▖▄▖▄▖▖▖ ▄▖▖ ▄▖
|
|
67
|
-
▌▞▖▌▙▘▌▌▐ ▐ ▙▌▄▖▌ ▌ ▐
|
|
68
|
-
▛ ▝▌▌▌▛▌▟▖▐ ▌▌ ▙▖▙▖▟▖
|
|
69
|
-
|
|
70
|
-
"""
|
|
71
|
-
|
|
72
|
-
logo_text = Text(wraith_pixel_logo, style="bold black on white")
|
|
70
|
+
logo_text = Text(assets.WRAITH_LOGO, style="bold black on white")
|
|
73
71
|
console.print(Align.center(logo_text))
|
|
74
72
|
|
|
75
73
|
sub_header_markup = (
|
|
@@ -79,11 +77,6 @@ def print_wraith_welcome(): # pragma: no cover
|
|
|
79
77
|
console.print(sub_header, justify="center")
|
|
80
78
|
console.print("\n")
|
|
81
79
|
|
|
82
|
-
console.print(
|
|
83
|
-
Text(f"Wraith Sovereign v{local_v}", style="white"), justify="center"
|
|
84
|
-
)
|
|
85
|
-
console.print("\n")
|
|
86
|
-
|
|
87
80
|
md = Markdown(SOVEREIGN_ADVISORY)
|
|
88
81
|
|
|
89
82
|
panel = Panel(
|
|
@@ -116,6 +109,9 @@ def main(
|
|
|
116
109
|
"""
|
|
117
110
|
[bold green]Wraith-CLI[/bold green]
|
|
118
111
|
The nervous system of the Sovereign Ghost Stack.
|
|
112
|
+
|
|
113
|
+
Orchestrate your local infrastructure, manage Gitea repositories,
|
|
114
|
+
and monitor system-wide health from a single unified entry point.
|
|
119
115
|
"""
|
|
120
116
|
|
|
121
117
|
if ctx.invoked_subcommand is None and not version:
|
|
@@ -127,7 +123,12 @@ def main(
|
|
|
127
123
|
|
|
128
124
|
@app.command()
|
|
129
125
|
def status():
|
|
130
|
-
"""
|
|
126
|
+
"""
|
|
127
|
+
Check the heartbeat of the OpenViking Stack.
|
|
128
|
+
|
|
129
|
+
Polls the configured VIKING_BASE_URL health
|
|
130
|
+
nendpoint to verify if the orchestrator API is responsive.
|
|
131
|
+
"""
|
|
131
132
|
url = f"{VIKING_URL}/health"
|
|
132
133
|
try:
|
|
133
134
|
response = requests.get(url, timeout=3)
|
|
@@ -144,7 +145,13 @@ def status():
|
|
|
144
145
|
|
|
145
146
|
@app.command()
|
|
146
147
|
def update():
|
|
147
|
-
"""
|
|
148
|
+
"""
|
|
149
|
+
Sync Wraith with the latest PyPI release.
|
|
150
|
+
|
|
151
|
+
Compares local versioning against the remote registry and
|
|
152
|
+
automatically triggers an upgrade via 'uv tool' if a newer
|
|
153
|
+
version is detected.
|
|
154
|
+
"""
|
|
148
155
|
try:
|
|
149
156
|
local_v = get_local_version("wraith-cli")
|
|
150
157
|
remote_v = qol.get_latest_version()
|
|
@@ -166,7 +173,13 @@ def ps(
|
|
|
166
173
|
bool, typer.Option("--all", "-a", help="Show all containers")
|
|
167
174
|
] = False,
|
|
168
175
|
):
|
|
169
|
-
"""
|
|
176
|
+
"""
|
|
177
|
+
List running containers with Sovereign styling.
|
|
178
|
+
|
|
179
|
+
Wraps 'docker ps' to provide a high-contrast,
|
|
180
|
+
readable table summarising service names,
|
|
181
|
+
container status, and source images.
|
|
182
|
+
"""
|
|
170
183
|
cmd = ["docker", "ps"]
|
|
171
184
|
if all:
|
|
172
185
|
cmd.append("-a")
|
|
@@ -198,7 +211,13 @@ def tail(
|
|
|
198
211
|
),
|
|
199
212
|
] = None,
|
|
200
213
|
):
|
|
201
|
-
"""
|
|
214
|
+
"""
|
|
215
|
+
Stream live logs from a specific service.
|
|
216
|
+
|
|
217
|
+
Connects to a running container to output real-time logs.
|
|
218
|
+
Resolves Compose paths via CLI flags,
|
|
219
|
+
environment variables, or local directory discovery.
|
|
220
|
+
|
|
202
221
|
Priority:
|
|
203
222
|
1. Passed flag --path
|
|
204
223
|
2. Env Var WRAITH_COMPOSE_PATH
|
|
@@ -218,7 +237,15 @@ def tail(
|
|
|
218
237
|
|
|
219
238
|
@app.command()
|
|
220
239
|
def runner_reset():
|
|
221
|
-
"""
|
|
240
|
+
"""
|
|
241
|
+
Repair hung or offline Gitea Action Runners.
|
|
242
|
+
|
|
243
|
+
Performs a nuclear reset: stops the container,
|
|
244
|
+
purges the local registration data, and restarts
|
|
245
|
+
the service to force a fresh handshake with Gitea.
|
|
246
|
+
|
|
247
|
+
Requires GITEA_COMPOSE_PATH as an envelope variable
|
|
248
|
+
"""
|
|
222
249
|
if not GITEA_PATH:
|
|
223
250
|
typer.secho("❌ Error: GITEA_COMPOSE_PATH not set in .env", fg="red")
|
|
224
251
|
raise typer.Exit(1)
|
|
@@ -249,7 +276,14 @@ def spawn(
|
|
|
249
276
|
..., help="Name of the new Ghost Stack repository"
|
|
250
277
|
),
|
|
251
278
|
):
|
|
252
|
-
"""
|
|
279
|
+
"""
|
|
280
|
+
🏭 Scaffold a new repository instantly.
|
|
281
|
+
|
|
282
|
+
Atomic scaffolding for new Ghost Stack repositories.
|
|
283
|
+
Clones a template, bleaches git history,
|
|
284
|
+
provisions a remote repository via API, and
|
|
285
|
+
pushes the initial commit in one sequence.
|
|
286
|
+
"""
|
|
253
287
|
if not all([GITEA_API_URL, GITEA_TOKEN, GITEA_TEMPLATE_URL]):
|
|
254
288
|
typer.secho(
|
|
255
289
|
"""❌ Missing Environment Variables!
|
|
@@ -305,20 +339,32 @@ def logs(
|
|
|
305
339
|
),
|
|
306
340
|
] = False,
|
|
307
341
|
):
|
|
308
|
-
"""
|
|
342
|
+
"""
|
|
343
|
+
Unified hardware health and logging portal.
|
|
344
|
+
|
|
345
|
+
Default behavior provides log-tailing instructions.
|
|
346
|
+
Using the --health flag triggers a deep-dive query
|
|
347
|
+
into the Scrutiny API for SMART drive data and disk temps.
|
|
348
|
+
"""
|
|
309
349
|
if health:
|
|
310
350
|
typer.echo("🔍 Polling Bare-Metal SMART data via Scrutiny...")
|
|
311
351
|
health_table = shield.get_scrutiny_health()
|
|
312
352
|
qol.console.print(health_table)
|
|
313
353
|
else:
|
|
314
354
|
typer.echo(
|
|
315
|
-
"Use 'wraith tail <
|
|
355
|
+
"Use 'wraith tail <svc>' for logs. Pass --health for drive health."
|
|
316
356
|
)
|
|
317
357
|
|
|
318
358
|
|
|
319
359
|
@app.command()
|
|
320
360
|
def mesh():
|
|
321
|
-
"""
|
|
361
|
+
"""
|
|
362
|
+
Map the Tailscale Ghost Mesh network.
|
|
363
|
+
|
|
364
|
+
Interrogates the Tailscale daemon (local or containerised)
|
|
365
|
+
to return a list of active peers, their internal IPs, and
|
|
366
|
+
current online/offline status.
|
|
367
|
+
"""
|
|
322
368
|
typer.echo("🌐 Querying Ghost Mesh (Tailscale)...")
|
|
323
369
|
mesh_table = shield.get_tailscale_mesh()
|
|
324
370
|
qol.console.print(mesh_table)
|
|
@@ -327,8 +373,11 @@ def mesh():
|
|
|
327
373
|
@app.command()
|
|
328
374
|
def audit():
|
|
329
375
|
"""
|
|
330
|
-
|
|
331
|
-
|
|
376
|
+
Execute a Sovereign security and compliance scan.
|
|
377
|
+
|
|
378
|
+
Audits the running stack for common vulnerabilities, including
|
|
379
|
+
containers running with root privileges and exposed ports
|
|
380
|
+
that deviate from the Registry Spec.
|
|
332
381
|
"""
|
|
333
382
|
typer.echo("🛡️ Initiating Sovereign Security Audit...")
|
|
334
383
|
audit_table = shield.run_security_audit()
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
3
|
import subprocess
|
|
4
|
+
|
|
4
5
|
import requests
|
|
5
6
|
from rich.table import Table
|
|
6
7
|
|
|
7
8
|
# Pull from .env, fallback to the Ghost Stack default port
|
|
8
9
|
SCRUTINY_URL = os.getenv("SCRUTINY_URL", "http://127.0.0.1:8090")
|
|
9
10
|
|
|
11
|
+
|
|
10
12
|
def get_scrutiny_health() -> Table:
|
|
11
13
|
"""Fetches SMART data from the Scrutiny API and formats it.
|
|
12
14
|
|
|
@@ -28,26 +30,32 @@ def get_scrutiny_health() -> Table:
|
|
|
28
30
|
response = requests.get(f"{SCRUTINY_URL}/api/summary", timeout=5)
|
|
29
31
|
response.raise_for_status()
|
|
30
32
|
data = response.json()
|
|
31
|
-
|
|
33
|
+
|
|
32
34
|
devices = data.get("data", {}).get("summary", {})
|
|
33
35
|
if not devices:
|
|
34
36
|
table.add_row("No devices found.", "-", "-", "-")
|
|
35
37
|
return table
|
|
36
38
|
|
|
37
|
-
for
|
|
39
|
+
for _wwn, device in devices.items():
|
|
38
40
|
name = device.get("device", {}).get("name", "Unknown")
|
|
39
41
|
capacity_bytes = device.get("device", {}).get("capacity", 0)
|
|
40
|
-
capacity_tb =
|
|
41
|
-
|
|
42
|
+
capacity_tb = (
|
|
43
|
+
f"{(capacity_bytes / (10**12)):.1f} TB"
|
|
44
|
+
if capacity_bytes
|
|
45
|
+
else "Unknown"
|
|
46
|
+
)
|
|
47
|
+
|
|
42
48
|
temp = str(device.get("smart", {}).get("temp", "N/A")) + "°C"
|
|
43
49
|
status = device.get("smart", {}).get("status", "Unknown")
|
|
44
|
-
|
|
50
|
+
|
|
45
51
|
# Sovereign styling for status
|
|
46
52
|
status_color = "green" if status.lower() == "passed" else "red"
|
|
47
|
-
formatted_status =
|
|
48
|
-
|
|
53
|
+
formatted_status = (
|
|
54
|
+
f"[{status_color}]{status.upper()}[/{status_color}]"
|
|
55
|
+
)
|
|
56
|
+
|
|
49
57
|
table.add_row(name, capacity_tb, temp, formatted_status)
|
|
50
|
-
|
|
58
|
+
|
|
51
59
|
except requests.exceptions.RequestException as e:
|
|
52
60
|
table.add_row(f"[red]API Error: {e}[/red]", "-", "-", "-")
|
|
53
61
|
|
|
@@ -55,11 +63,7 @@ def get_scrutiny_health() -> Table:
|
|
|
55
63
|
|
|
56
64
|
|
|
57
65
|
def get_tailscale_mesh() -> Table:
|
|
58
|
-
"""Pulls Tailscale status
|
|
59
|
-
|
|
60
|
-
Returns:
|
|
61
|
-
Table: A rich Table object containing Tailscale mesh peer status.
|
|
62
|
-
"""
|
|
66
|
+
"""Pulls Tailscale status from a Docker container and formats peers."""
|
|
63
67
|
table = Table(
|
|
64
68
|
title="Ghost Mesh (Tailscale)",
|
|
65
69
|
border_style="bright_black",
|
|
@@ -70,30 +74,49 @@ def get_tailscale_mesh() -> Table:
|
|
|
70
74
|
table.add_column("OS")
|
|
71
75
|
table.add_column("Status")
|
|
72
76
|
|
|
77
|
+
# The command shifted to target the container
|
|
78
|
+
# docker exec <container_name> tailscale status --json
|
|
79
|
+
cmd = ["docker", "exec", "tailscale", "tailscale", "status", "--json"]
|
|
80
|
+
|
|
73
81
|
try:
|
|
74
82
|
result = subprocess.run(
|
|
75
|
-
|
|
83
|
+
cmd,
|
|
76
84
|
capture_output=True,
|
|
77
85
|
text=True,
|
|
78
86
|
check=True,
|
|
79
87
|
)
|
|
88
|
+
|
|
80
89
|
data = json.loads(result.stdout)
|
|
81
|
-
|
|
82
90
|
peers = data.get("Peer", {})
|
|
83
|
-
|
|
91
|
+
|
|
92
|
+
if not peers:
|
|
93
|
+
table.add_row(
|
|
94
|
+
"No peers found", "-", "-", "[yellow]Solo Node[/yellow]"
|
|
95
|
+
)
|
|
96
|
+
return table
|
|
97
|
+
|
|
98
|
+
for peer_data in peers.values():
|
|
84
99
|
hostname = peer_data.get("HostName", "Unknown")
|
|
85
|
-
|
|
100
|
+
ips = peer_data.get("TailscaleIPs", ["Unknown"])
|
|
101
|
+
ip = ips[0] if ips else "Unknown"
|
|
86
102
|
os_name = peer_data.get("OS", "Unknown")
|
|
87
|
-
is_online = peer_data.get("Online", False)
|
|
88
103
|
|
|
89
104
|
status_str = (
|
|
90
105
|
"[green]Online[/green]"
|
|
91
|
-
if
|
|
106
|
+
if peer_data.get("Online")
|
|
92
107
|
else "[bright_black]Offline[/bright_black]"
|
|
93
108
|
)
|
|
94
109
|
|
|
95
110
|
table.add_row(hostname, ip, os_name, status_str)
|
|
96
|
-
|
|
111
|
+
|
|
112
|
+
except subprocess.CalledProcessError:
|
|
113
|
+
# This triggers if the container 'tailscale' isn't running
|
|
114
|
+
table.add_row(
|
|
115
|
+
"[red]Error: Tailscale container not found or stopped[/red]",
|
|
116
|
+
"-",
|
|
117
|
+
"-",
|
|
118
|
+
"-",
|
|
119
|
+
)
|
|
97
120
|
except Exception as e:
|
|
98
121
|
table.add_row(f"[red]Mesh Error: {e}[/red]", "-", "-", "-")
|
|
99
122
|
|
|
@@ -141,7 +164,7 @@ def run_security_audit() -> Table:
|
|
|
141
164
|
|
|
142
165
|
for c in containers:
|
|
143
166
|
name = c["Name"].lstrip("/")
|
|
144
|
-
|
|
167
|
+
|
|
145
168
|
# Check User
|
|
146
169
|
user = c["Config"].get("User", "")
|
|
147
170
|
if not user or user == "0" or user == "root":
|
|
@@ -150,7 +173,7 @@ def run_security_audit() -> Table:
|
|
|
150
173
|
else:
|
|
151
174
|
user_disp = f"[green]{user}[/green]"
|
|
152
175
|
is_root = False
|
|
153
|
-
|
|
176
|
+
|
|
154
177
|
# Check Ports (Simplified: grabs bindings)
|
|
155
178
|
ports = c["NetworkSettings"].get("Ports", {})
|
|
156
179
|
exposed = []
|
|
@@ -162,13 +185,14 @@ def run_security_audit() -> Table:
|
|
|
162
185
|
# Flag if exposed globally (0.0.0.0) vs. locally.
|
|
163
186
|
if host_ip == "0.0.0.0" or host_ip == "":
|
|
164
187
|
exposed.append(
|
|
165
|
-
f"[yellow]{host_port}->{port}
|
|
188
|
+
f"[yellow]{host_port}->{port} "
|
|
189
|
+
f"(Global)[/yellow]"
|
|
166
190
|
)
|
|
167
191
|
else:
|
|
168
192
|
exposed.append(f"{host_port}->{port}")
|
|
169
|
-
|
|
193
|
+
|
|
170
194
|
port_disp = ", ".join(exposed) if exposed else "Internal Only"
|
|
171
|
-
|
|
195
|
+
|
|
172
196
|
# Determine Verdict
|
|
173
197
|
if is_root and "Global" in port_disp:
|
|
174
198
|
verdict = "[red]CRITICAL: Root + Global Port[/red]"
|
|
@@ -280,7 +280,7 @@ def test_logs_default():
|
|
|
280
280
|
"""Test logs command with no flags."""
|
|
281
281
|
result = runner.invoke(app, ["logs"])
|
|
282
282
|
assert result.exit_code == 0
|
|
283
|
-
assert "Use 'wraith tail <
|
|
283
|
+
assert "Use 'wraith tail <svc>'" in result.stdout
|
|
284
284
|
|
|
285
285
|
|
|
286
286
|
@patch("wraith_cli.shield.get_tailscale_mesh")
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import subprocess
|
|
2
3
|
from unittest.mock import Mock, patch
|
|
3
4
|
|
|
4
5
|
import pytest
|
|
@@ -91,6 +92,32 @@ def test_get_scrutiny_health_missing_data(mock_get):
|
|
|
91
92
|
assert "FAILED" in str(cells[3])
|
|
92
93
|
|
|
93
94
|
|
|
95
|
+
@patch("wraith_cli.shield.requests.get")
|
|
96
|
+
def test_get_scrutiny_health_missing_smart_data(mock_get):
|
|
97
|
+
"""Test Scrutiny data with missing 'smart' section."""
|
|
98
|
+
mock_response = Mock()
|
|
99
|
+
mock_response.json.return_value = {
|
|
100
|
+
"data": {
|
|
101
|
+
"summary": {
|
|
102
|
+
"WWN1": {
|
|
103
|
+
"device": {"name": "sdb", "capacity": 2 * 10**12},
|
|
104
|
+
# 'smart' key is entirely missing
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
mock_response.raise_for_status.return_value = None
|
|
110
|
+
mock_get.return_value = mock_response
|
|
111
|
+
|
|
112
|
+
table = get_scrutiny_health()
|
|
113
|
+
assert len(table.rows) == 1
|
|
114
|
+
cells = [list(col.cells)[0] for col in table.columns]
|
|
115
|
+
assert str(cells[0]) == "sdb"
|
|
116
|
+
assert str(cells[1]) == "2.0 TB"
|
|
117
|
+
assert str(cells[2]) == "N/A°C"
|
|
118
|
+
assert "UNKNOWN" in str(cells[3])
|
|
119
|
+
|
|
120
|
+
|
|
94
121
|
@patch("wraith_cli.shield.subprocess.run")
|
|
95
122
|
def test_get_tailscale_mesh_success(mock_run):
|
|
96
123
|
"""Test successful retrieval of Tailscale mesh data."""
|
|
@@ -131,14 +158,80 @@ def test_get_tailscale_mesh_success(mock_run):
|
|
|
131
158
|
|
|
132
159
|
|
|
133
160
|
@patch("wraith_cli.shield.subprocess.run")
|
|
134
|
-
def
|
|
135
|
-
"""Test handling of
|
|
136
|
-
mock_run.side_effect = Exception("
|
|
161
|
+
def test_get_tailscale_mesh_generic_error(mock_run):
|
|
162
|
+
"""Test handling of a generic error when running tailscale command."""
|
|
163
|
+
mock_run.side_effect = Exception("Generic error for tailscale")
|
|
164
|
+
|
|
165
|
+
table = get_tailscale_mesh()
|
|
166
|
+
assert len(table.rows) == 1
|
|
167
|
+
cells = [list(col.cells)[0] for col in table.columns]
|
|
168
|
+
assert "Mesh Error: Generic error for tailscale" in str(cells[0])
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@patch("wraith_cli.shield.subprocess.run")
|
|
172
|
+
def test_get_tailscale_mesh_called_process_error(mock_run):
|
|
173
|
+
"""Test handling of subprocess.CalledProcessError for tailscale."""
|
|
174
|
+
mock_run.side_effect = subprocess.CalledProcessError(1, ["tailscale"])
|
|
175
|
+
|
|
176
|
+
table = get_tailscale_mesh()
|
|
177
|
+
assert len(table.rows) == 1
|
|
178
|
+
cells = [list(col.cells)[0] for col in table.columns]
|
|
179
|
+
assert ("Error: Tailscale container not found or stopped"
|
|
180
|
+
in str(cells[0]))
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@patch("wraith_cli.shield.subprocess.run")
|
|
184
|
+
def test_get_tailscale_mesh_no_peers(mock_run):
|
|
185
|
+
"""Test Tailscale mesh when no peers are found."""
|
|
186
|
+
mock_process = Mock()
|
|
187
|
+
mock_process.stdout = json.dumps({"Peer": {}}) # Empty peers
|
|
188
|
+
mock_run.return_value = mock_process
|
|
137
189
|
|
|
138
190
|
table = get_tailscale_mesh()
|
|
139
191
|
assert len(table.rows) == 1
|
|
140
192
|
cells = [list(col.cells)[0] for col in table.columns]
|
|
141
|
-
assert
|
|
193
|
+
assert str(cells[0]) == "No peers found"
|
|
194
|
+
assert "[yellow]Solo Node[/yellow]" in str(cells[3])
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@patch("wraith_cli.shield.subprocess.run")
|
|
198
|
+
def test_get_tailscale_mesh_empty_ips(mock_run):
|
|
199
|
+
"""Test Tailscale mesh with empty TailscaleIPs list."""
|
|
200
|
+
mock_process = Mock()
|
|
201
|
+
mock_process.stdout = json.dumps(
|
|
202
|
+
{
|
|
203
|
+
"Peer": {
|
|
204
|
+
"peer1": {
|
|
205
|
+
"HostName": "node1",
|
|
206
|
+
"TailscaleIPs": [], # Empty IPs
|
|
207
|
+
"OS": "Linux",
|
|
208
|
+
"Online": True,
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
mock_run.return_value = mock_process
|
|
214
|
+
|
|
215
|
+
table = get_tailscale_mesh()
|
|
216
|
+
assert len(table.rows) == 1
|
|
217
|
+
cells = [list(col.cells)[0] for col in table.columns]
|
|
218
|
+
assert str(cells[0]) == "node1"
|
|
219
|
+
assert str(cells[1]) == "Unknown"
|
|
220
|
+
assert str(cells[2]) == "Linux"
|
|
221
|
+
assert "Online" in str(cells[3])
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@patch("wraith_cli.shield.subprocess.run")
|
|
225
|
+
def test_get_tailscale_mesh_malformed_json(mock_run):
|
|
226
|
+
"""Test Tailscale mesh with malformed JSON output."""
|
|
227
|
+
mock_process = Mock()
|
|
228
|
+
mock_process.stdout = "this is not json"
|
|
229
|
+
mock_run.return_value = mock_process
|
|
230
|
+
|
|
231
|
+
table = get_tailscale_mesh()
|
|
232
|
+
assert len(table.rows) == 1
|
|
233
|
+
cells = [list(col.cells)[0] for col in table.columns]
|
|
234
|
+
assert "Mesh Error: Expecting value" in str(cells[0])
|
|
142
235
|
|
|
143
236
|
|
|
144
237
|
@patch("wraith_cli.shield.subprocess.run")
|
|
@@ -155,9 +248,12 @@ def test_run_security_audit_no_containers(mock_run):
|
|
|
155
248
|
|
|
156
249
|
def mock_container_inspect(user, ports):
|
|
157
250
|
"""Helper to create a mock container inspect JSON."""
|
|
251
|
+
config_data = {}
|
|
252
|
+
if user is not None:
|
|
253
|
+
config_data["User"] = user
|
|
158
254
|
return {
|
|
159
255
|
"Name": "/test-container",
|
|
160
|
-
"Config":
|
|
256
|
+
"Config": config_data,
|
|
161
257
|
"NetworkSettings": {"Ports": ports},
|
|
162
258
|
}
|
|
163
259
|
|
|
@@ -201,6 +297,13 @@ def mock_container_inspect(user, ports):
|
|
|
201
297
|
"SECURE",
|
|
202
298
|
),
|
|
203
299
|
("testuser", {}, "[green]testuser[/green]", "Internal Only", "SECURE"),
|
|
300
|
+
(
|
|
301
|
+
None, # Simulate missing 'User' key in Config
|
|
302
|
+
{"80/tcp": [{"HostIp": "127.0.0.1", "HostPort": "8080"}]},
|
|
303
|
+
"[red]ROOT[/red]",
|
|
304
|
+
"8080->80/tcp",
|
|
305
|
+
"WARNING: Root Context",
|
|
306
|
+
),
|
|
204
307
|
],
|
|
205
308
|
)
|
|
206
309
|
@patch("wraith_cli.shield.subprocess.run")
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
repos:
|
|
2
|
-
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
-
rev: v4.4.0
|
|
4
|
-
hooks:
|
|
5
|
-
- id: trailing-whitespace
|
|
6
|
-
- id: end-of-file-fixer
|
|
7
|
-
- id: check-yaml
|
|
8
|
-
- id: check-added-large-files
|
|
9
|
-
|
|
10
|
-
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
11
|
-
rev: v0.9.6
|
|
12
|
-
hooks:
|
|
13
|
-
- id: ruff
|
|
14
|
-
args: ["--fix"]
|
|
15
|
-
- id: ruff-format
|
|
16
|
-
|
|
17
|
-
- repo: local
|
|
18
|
-
hooks:
|
|
19
|
-
- id: ty
|
|
20
|
-
name: ty
|
|
21
|
-
entry: uv run ty check
|
|
22
|
-
language: system
|
|
23
|
-
types: [python]
|
|
24
|
-
pass_filenames: false
|
|
25
|
-
|
|
26
|
-
- repo: https://github.com/Yelp/detect-secrets
|
|
27
|
-
rev: v1.5.0
|
|
28
|
-
hooks:
|
|
29
|
-
- id: detect-secrets
|
|
30
|
-
args: ['--baseline', '.secrets.baseline']
|
|
31
|
-
exclude: package-lock.json
|
|
32
|
-
|
|
33
|
-
- repo: local
|
|
34
|
-
hooks:
|
|
35
|
-
- id: pytest-coverage
|
|
36
|
-
name: pytest-coverage
|
|
37
|
-
entry: ./bin/run_tests.sh
|
|
38
|
-
language: system
|
|
39
|
-
pass_filenames: false
|
|
40
|
-
always_run: true
|
|
41
|
-
|
|
42
|
-
- id: commitizen
|
|
43
|
-
name: commitizen check
|
|
44
|
-
entry: uv run cz check --commit-msg-file
|
|
45
|
-
language: system
|
|
46
|
-
stages: [commit-msg]
|
wraith_cli-1.3.0/docs/usage.md
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
# 📖 Operational Usage Guide
|
|
2
|
-
|
|
3
|
-
**Wraith** is designed to be intuitive and fast. Make a coffee, grab a Hob-Nob, and put down your glass rectangle — it's time to orchestrate.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 🕹️ Core Commands
|
|
8
|
-
|
|
9
|
-
### 1. The Global Manifest
|
|
10
|
-
|
|
11
|
-
To see all available commands, options, and the Sovereign splash screen:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
wraith --help
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
### 2. The Ghost Factory (Scaffolding)
|
|
18
|
-
|
|
19
|
-
This is the flagship feature. Instead of manual setup, we spawn. This command clones your template, bleaches the history, provisions a Gitea repo, and pushes the initial commit in one atomic action.
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
wraith spawn <project-name>
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
> **Pro Tip:** Ensure your `GITEA_TOKEN` is set in your `.env` to avoid the manual
|
|
26
|
-
> password hang.
|
|
27
|
-
|
|
28
|
-
### 3. Stack Observability
|
|
29
|
-
|
|
30
|
-
Monitor your Sovereign containers without leaving the terminal.
|
|
31
|
-
|
|
32
|
-
- **Process List:** `wraith ps` — Shows names, status, and images in Sovereign Dark style.
|
|
33
|
-
- **Live Logs:** `wraith tail <service-name>` — Tails logs for a specific container.
|
|
34
|
-
- **Heartbeat:** `wraith status` — Checks if the OpenViking API is responding.
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## 🛠️ Maintenance & Defence
|
|
39
|
-
|
|
40
|
-
### 🔄 Staying Current
|
|
41
|
-
|
|
42
|
-
Wraith is self-aware. To check for updates on PyPI and upgrade via `uv`:
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
wraith update
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### 🧹 Runner Recovery
|
|
49
|
-
|
|
50
|
-
If a Gitea Action Runner hangs or goes "Offline" in the UI, use the nuclear option to wipe its registration and restart the service:
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
wraith runner-reset
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
> *Note: Requires `GITEA_COMPOSE_PATH` to be defined.*
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
## 🔐 Configuration
|
|
61
|
-
|
|
62
|
-
Wraith prioritises environment variables for security. You can define these in a `.env` file in your project root or your global shell profile.
|
|
63
|
-
|
|
64
|
-
| Variable | Description | Required For |
|
|
65
|
-
|------------------------|------------------------------------------------------|----------------|
|
|
66
|
-
| `GITEA_API_URL` | Your Gitea instance (e.g., `https://git.domain.com`) | `spawn` |
|
|
67
|
-
| `GITEA_TOKEN` | Your Personal Access Token (PAT) | `spawn` |
|
|
68
|
-
| `GITEA_TEMPLATE_URL` | HTTPS URL of your source `ghost-template` | `spawn` |
|
|
69
|
-
| `WRAITH_COMPOSE_PATH` | Path to your main stack directory | `ps`, `tail` |
|
|
70
|
-
| `VIKING_BASE_URL` | Heartbeat monitoring endpoint | `status` |
|
|
71
|
-
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
## ⌨️ Advanced Flags
|
|
75
|
-
|
|
76
|
-
| Flag | Description |
|
|
77
|
-
|-----------------------------------------|------------------------------------------|
|
|
78
|
-
| `wraith --version` | Print the current installed version. |
|
|
79
|
-
| `wraith ps --all` | Show all containers, including stopped. |
|
|
80
|
-
| `wraith tail <svc> --path /custom/path` | Override the default Compose path. |
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|