exosphere-cli 3.0.0__tar.gz → 3.0.2__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.
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/PKG-INFO +16 -11
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/README.md +12 -8
- exosphere_cli-3.0.2/pyproject.toml +132 -0
- exosphere_cli-3.0.0/pyproject.toml → exosphere_cli-3.0.2/pyproject.toml.orig +5 -4
- exosphere_cli-3.0.2/src/exosphere/__init__.py +29 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/commands/config.py +4 -4
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/commands/host.py +11 -10
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/commands/inventory.py +31 -28
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/commands/report.py +7 -7
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/commands/utils.py +4 -3
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/commands/version.py +6 -5
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/config.py +33 -10
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/data.py +4 -4
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/editing.py +4 -1
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/errors.py +3 -1
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/fspaths.py +1 -1
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/inventory.py +12 -6
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/main.py +14 -7
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/migrations.py +12 -9
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/objects.py +97 -21
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/pipelining.py +3 -3
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/providers/api.py +2 -2
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/providers/debian.py +2 -1
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/providers/factory.py +5 -3
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/providers/freebsd.py +9 -9
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/providers/redhat.py +11 -12
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/repl.py +14 -3
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/reporting.py +2 -2
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/ui/app.py +18 -11
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/ui/dashboard.py +15 -10
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/ui/elements.py +5 -3
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/ui/inventory.py +15 -10
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/ui/logs.py +11 -9
- exosphere_cli-3.0.0/src/exosphere/__init__.py +0 -14
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/COPYRIGHT +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/LICENSE +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/LICENSES/Apache-2.0.txt +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/LICENSES/CC-BY-4.0.txt +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/LICENSES/MIT.txt +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/LICENSES/OFL-1.1.txt +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/cli.py +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/commands/__init__.py +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/commands/connections.py +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/commands/sudo.py +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/commands/ui.py +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/context.py +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/database.py +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/providers/__init__.py +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/providers/openbsd.py +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/runners.py +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/schema/__init__.py +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/schema/host-report.schema.json +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/security.py +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/setup/__init__.py +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/setup/detect.py +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/templates/report.html.j2 +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/templates/report.md.j2 +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/templates/report.txt.j2 +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/ui/__init__.py +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/ui/context.py +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/ui/messages.py +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/ui/palette.py +0 -0
- {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/ui/style.tcss +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: exosphere-cli
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.2
|
|
4
4
|
Summary: CLI/TUI driven patch reporting for remote Unix-like systems.
|
|
5
5
|
Author: Alexandre Gauthier
|
|
6
6
|
Author-email: Alexandre Gauthier <alex@underwares.org>
|
|
@@ -30,11 +30,12 @@ Requires-Dist: prompt-toolkit>=3.0.51
|
|
|
30
30
|
Requires-Dist: jinja2>=3.1.6
|
|
31
31
|
Requires-Dist: packaging>=24.0
|
|
32
32
|
Requires-Dist: rich>=14.1.0
|
|
33
|
-
Requires-Dist: cyclopts>=4.16.1
|
|
33
|
+
Requires-Dist: cyclopts>=4.16.1,<5.0.0
|
|
34
34
|
Requires-Dist: filelock>=3.29.4
|
|
35
35
|
Requires-Dist: pydantic>=2.12
|
|
36
36
|
Requires-Python: >=3.13
|
|
37
|
-
Project-URL: homepage, https://exosphere.
|
|
37
|
+
Project-URL: homepage, https://exosphere.tools
|
|
38
|
+
Project-URL: documentation, https://docs.exosphere.tools
|
|
38
39
|
Project-URL: repository, https://github.com/mrdaemon/exosphere
|
|
39
40
|
Project-URL: issues, https://github.com/mrdaemon/exosphere/issues
|
|
40
41
|
Description-Content-Type: text/markdown
|
|
@@ -63,8 +64,8 @@ on remote hosts.
|
|
|
63
64
|
If you have SSH access to the hosts and your keypairs are loaded in an SSH Agent,
|
|
64
65
|
you are good to go!
|
|
65
66
|
|
|
66
|
-
Simply follow the [Quickstart Guide](https://exosphere.
|
|
67
|
-
or see [the documentation](https://exosphere.
|
|
67
|
+
Simply follow the [Quickstart Guide](https://docs.exosphere.tools/en/stable/quickstart.html),
|
|
68
|
+
or see [the documentation](https://docs.exosphere.tools/en/stable/) to get started.
|
|
68
69
|
|
|
69
70
|
## Key Features
|
|
70
71
|
|
|
@@ -103,7 +104,7 @@ This includes network equipment with proprietary operating systems, etc.
|
|
|
103
104
|
## Documentation
|
|
104
105
|
|
|
105
106
|
For installation instructions, configuration and usage examples,
|
|
106
|
-
[full documentation](https://exosphere.
|
|
107
|
+
[full documentation](https://docs.exosphere.tools/) is available.
|
|
107
108
|
|
|
108
109
|
## Development
|
|
109
110
|
|
|
@@ -149,7 +150,6 @@ uv run textual run --dev -c exosphere ui
|
|
|
149
150
|
Congratulations! Editing any of the `.tcss` files in the `ui/` directory will
|
|
150
151
|
reflect changes immediately.
|
|
151
152
|
|
|
152
|
-
|
|
153
153
|
### Documentation Editing Quick Start
|
|
154
154
|
|
|
155
155
|
To edit the documentation, you can use the following commands:
|
|
@@ -188,8 +188,10 @@ tasks are defined in the `pyproject.toml` file under the `[tool.poe.tasks]` tabl
|
|
|
188
188
|
|
|
189
189
|
| path | description |
|
|
190
190
|
| ---- | ----------- |
|
|
191
|
+
| `LICENSES/` | Full text of every license used by the project and its assets |
|
|
191
192
|
| `docs/` | Sphinx documentation source tree |
|
|
192
193
|
| `docs/source/_ext/` | Custom Sphinx extensions for the project |
|
|
194
|
+
| `docs/source/changelog/` | Per-release changelog entries (markdown) and their assets |
|
|
193
195
|
| `examples/` | Example configuration files and reports |
|
|
194
196
|
| `scripts/` | Utilitarian scripts for dev and maintenance |
|
|
195
197
|
| `src/` | Main source code for the application |
|
|
@@ -224,13 +226,16 @@ Paths below are relative to `src/exosphere/` unless otherwise noted.
|
|
|
224
226
|
| `context.py` | Context management for shared state across commands and UI |
|
|
225
227
|
| `data.py` | Data models and structures for serialization and exchange |
|
|
226
228
|
| `database.py` | Cache system for serialization |
|
|
229
|
+
| `editing.py` | External editor helper module, shared between CLI and UI |
|
|
227
230
|
| `errors.py` | Exception classes and general error messages |
|
|
231
|
+
| `fspaths.py` | Platform-appropriate filesystem paths module, for state |
|
|
228
232
|
| `inventory.py` | Inventory management subsystem |
|
|
229
233
|
| `migrations.py` | Cache format migration processes |
|
|
230
234
|
| `objects.py` | Main objects for representing Hosts, and most of the relevant logic |
|
|
231
235
|
| `pipelining.py` | SSH pipelining implementation, including reaper thread |
|
|
232
236
|
| `repl.py` | REPL module for interactive CLI usage |
|
|
233
237
|
| `reporting.py` | Reporting subsystem, including templates and formatters |
|
|
238
|
+
| `runners.py` | SSH runners module, handles setting up remote POSIX environment |
|
|
234
239
|
| `security.py` | Sudo management subsystem, including policy and utilities |
|
|
235
240
|
|
|
236
241
|
Generally, most of the things Exosphere does to hosts (including connection management
|
|
@@ -245,9 +250,9 @@ Paths below are relative to `src/exosphere/` unless otherwise noted.
|
|
|
245
250
|
| `ui/app.py` | Main Textual application class and entry point for the UI |
|
|
246
251
|
| `ui/context.py` | UI Context management for shared state across UI components |
|
|
247
252
|
| `ui/elements.py` | Shared UI elements, including task runners |
|
|
248
|
-
| `ui/dashboard.py` | Dashboard
|
|
249
|
-
| `ui/inventory.py` | Inventory
|
|
250
|
-
| `ui/logs.py` | Logs
|
|
253
|
+
| `ui/dashboard.py` | Dashboard screen implementation |
|
|
254
|
+
| `ui/inventory.py` | Inventory screen implementation |
|
|
255
|
+
| `ui/logs.py` | Logs screen implementation |
|
|
251
256
|
| `ui/messages.py` | Screen refresh and message passing system |
|
|
252
257
|
| `ui/palette.py` | Command palette providers and implementations |
|
|
253
258
|
|
|
@@ -257,7 +262,7 @@ The TCSS for all of it is in a single file under `ui/style.tcss`.
|
|
|
257
262
|
|
|
258
263
|
This use case is not currently well supported, but it is possible to use Exosphere as a library.
|
|
259
264
|
The documentation for this (alongside actual examples) is still a WIP, but you can
|
|
260
|
-
refer to the [Online API Documentation](https://exosphere.
|
|
265
|
+
refer to the [Online API Documentation](https://docs.exosphere.tools/en/stable/api/index.html)
|
|
261
266
|
for the core functionality and objects that are considered public.
|
|
262
267
|
|
|
263
268
|
## LLM/"AI" Contributions Policy
|
|
@@ -22,8 +22,8 @@ on remote hosts.
|
|
|
22
22
|
If you have SSH access to the hosts and your keypairs are loaded in an SSH Agent,
|
|
23
23
|
you are good to go!
|
|
24
24
|
|
|
25
|
-
Simply follow the [Quickstart Guide](https://exosphere.
|
|
26
|
-
or see [the documentation](https://exosphere.
|
|
25
|
+
Simply follow the [Quickstart Guide](https://docs.exosphere.tools/en/stable/quickstart.html),
|
|
26
|
+
or see [the documentation](https://docs.exosphere.tools/en/stable/) to get started.
|
|
27
27
|
|
|
28
28
|
## Key Features
|
|
29
29
|
|
|
@@ -62,7 +62,7 @@ This includes network equipment with proprietary operating systems, etc.
|
|
|
62
62
|
## Documentation
|
|
63
63
|
|
|
64
64
|
For installation instructions, configuration and usage examples,
|
|
65
|
-
[full documentation](https://exosphere.
|
|
65
|
+
[full documentation](https://docs.exosphere.tools/) is available.
|
|
66
66
|
|
|
67
67
|
## Development
|
|
68
68
|
|
|
@@ -108,7 +108,6 @@ uv run textual run --dev -c exosphere ui
|
|
|
108
108
|
Congratulations! Editing any of the `.tcss` files in the `ui/` directory will
|
|
109
109
|
reflect changes immediately.
|
|
110
110
|
|
|
111
|
-
|
|
112
111
|
### Documentation Editing Quick Start
|
|
113
112
|
|
|
114
113
|
To edit the documentation, you can use the following commands:
|
|
@@ -147,8 +146,10 @@ tasks are defined in the `pyproject.toml` file under the `[tool.poe.tasks]` tabl
|
|
|
147
146
|
|
|
148
147
|
| path | description |
|
|
149
148
|
| ---- | ----------- |
|
|
149
|
+
| `LICENSES/` | Full text of every license used by the project and its assets |
|
|
150
150
|
| `docs/` | Sphinx documentation source tree |
|
|
151
151
|
| `docs/source/_ext/` | Custom Sphinx extensions for the project |
|
|
152
|
+
| `docs/source/changelog/` | Per-release changelog entries (markdown) and their assets |
|
|
152
153
|
| `examples/` | Example configuration files and reports |
|
|
153
154
|
| `scripts/` | Utilitarian scripts for dev and maintenance |
|
|
154
155
|
| `src/` | Main source code for the application |
|
|
@@ -183,13 +184,16 @@ Paths below are relative to `src/exosphere/` unless otherwise noted.
|
|
|
183
184
|
| `context.py` | Context management for shared state across commands and UI |
|
|
184
185
|
| `data.py` | Data models and structures for serialization and exchange |
|
|
185
186
|
| `database.py` | Cache system for serialization |
|
|
187
|
+
| `editing.py` | External editor helper module, shared between CLI and UI |
|
|
186
188
|
| `errors.py` | Exception classes and general error messages |
|
|
189
|
+
| `fspaths.py` | Platform-appropriate filesystem paths module, for state |
|
|
187
190
|
| `inventory.py` | Inventory management subsystem |
|
|
188
191
|
| `migrations.py` | Cache format migration processes |
|
|
189
192
|
| `objects.py` | Main objects for representing Hosts, and most of the relevant logic |
|
|
190
193
|
| `pipelining.py` | SSH pipelining implementation, including reaper thread |
|
|
191
194
|
| `repl.py` | REPL module for interactive CLI usage |
|
|
192
195
|
| `reporting.py` | Reporting subsystem, including templates and formatters |
|
|
196
|
+
| `runners.py` | SSH runners module, handles setting up remote POSIX environment |
|
|
193
197
|
| `security.py` | Sudo management subsystem, including policy and utilities |
|
|
194
198
|
|
|
195
199
|
Generally, most of the things Exosphere does to hosts (including connection management
|
|
@@ -204,9 +208,9 @@ Paths below are relative to `src/exosphere/` unless otherwise noted.
|
|
|
204
208
|
| `ui/app.py` | Main Textual application class and entry point for the UI |
|
|
205
209
|
| `ui/context.py` | UI Context management for shared state across UI components |
|
|
206
210
|
| `ui/elements.py` | Shared UI elements, including task runners |
|
|
207
|
-
| `ui/dashboard.py` | Dashboard
|
|
208
|
-
| `ui/inventory.py` | Inventory
|
|
209
|
-
| `ui/logs.py` | Logs
|
|
211
|
+
| `ui/dashboard.py` | Dashboard screen implementation |
|
|
212
|
+
| `ui/inventory.py` | Inventory screen implementation |
|
|
213
|
+
| `ui/logs.py` | Logs screen implementation |
|
|
210
214
|
| `ui/messages.py` | Screen refresh and message passing system |
|
|
211
215
|
| `ui/palette.py` | Command palette providers and implementations |
|
|
212
216
|
|
|
@@ -216,7 +220,7 @@ The TCSS for all of it is in a single file under `ui/style.tcss`.
|
|
|
216
220
|
|
|
217
221
|
This use case is not currently well supported, but it is possible to use Exosphere as a library.
|
|
218
222
|
The documentation for this (alongside actual examples) is still a WIP, but you can
|
|
219
|
-
refer to the [Online API Documentation](https://exosphere.
|
|
223
|
+
refer to the [Online API Documentation](https://docs.exosphere.tools/en/stable/api/index.html)
|
|
220
224
|
for the core functionality and objects that are considered public.
|
|
221
225
|
|
|
222
226
|
## LLM/"AI" Contributions Policy
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "exosphere-cli"
|
|
3
|
+
version = "3.0.2"
|
|
4
|
+
description = "CLI/TUI driven patch reporting for remote Unix-like systems."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.13"
|
|
7
|
+
classifiers = [
|
|
8
|
+
"Development Status :: 5 - Production/Stable",
|
|
9
|
+
"Environment :: Console",
|
|
10
|
+
"Intended Audience :: System Administrators",
|
|
11
|
+
"Natural Language :: English",
|
|
12
|
+
"Operating System :: OS Independent",
|
|
13
|
+
"Programming Language :: Python :: 3.13",
|
|
14
|
+
"Programming Language :: Python :: 3.14",
|
|
15
|
+
"Topic :: System :: Monitoring",
|
|
16
|
+
"Topic :: System :: Systems Administration",
|
|
17
|
+
"Topic :: Utilities",
|
|
18
|
+
"Typing :: Typed",
|
|
19
|
+
]
|
|
20
|
+
license = "MIT"
|
|
21
|
+
license-files = [
|
|
22
|
+
"LICENSE",
|
|
23
|
+
"LICENSES/*",
|
|
24
|
+
"COPYRIGHT",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"fabric>=3.2.2",
|
|
28
|
+
"textual>=6.7.0",
|
|
29
|
+
"pyyaml>=6.0.3",
|
|
30
|
+
"platformdirs>=4.3.8",
|
|
31
|
+
"prompt-toolkit>=3.0.51",
|
|
32
|
+
"jinja2>=3.1.6",
|
|
33
|
+
"packaging>=24.0",
|
|
34
|
+
"rich>=14.1.0",
|
|
35
|
+
"cyclopts>=4.16.1,<5.0.0",
|
|
36
|
+
"filelock>=3.29.4",
|
|
37
|
+
"pydantic>=2.12",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[[project.authors]]
|
|
41
|
+
name = "Alexandre Gauthier"
|
|
42
|
+
email = "alex@underwares.org"
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
homepage = "https://exosphere.tools"
|
|
46
|
+
documentation = "https://docs.exosphere.tools"
|
|
47
|
+
repository = "https://github.com/mrdaemon/exosphere"
|
|
48
|
+
issues = "https://github.com/mrdaemon/exosphere/issues"
|
|
49
|
+
|
|
50
|
+
[project.scripts]
|
|
51
|
+
exosphere = "exosphere.main:main"
|
|
52
|
+
|
|
53
|
+
[dependency-groups]
|
|
54
|
+
dev = [
|
|
55
|
+
"fabric[pytest]>=3.2.2",
|
|
56
|
+
"jsonschema>=4.25.1",
|
|
57
|
+
"poethepoet>=0.33.1",
|
|
58
|
+
"pyright>=1.1.400",
|
|
59
|
+
"pytest>=8.4.2",
|
|
60
|
+
"pytest-asyncio>=1.2.0",
|
|
61
|
+
"pytest-cov>=6.1.1",
|
|
62
|
+
"pytest-json-ctrf>=0.3.5",
|
|
63
|
+
"pytest-mock>=3.14.1",
|
|
64
|
+
"sphinx-rtd-theme>=3.0.2",
|
|
65
|
+
"ruff>=0.15.0",
|
|
66
|
+
"sphinx>=8.2.3",
|
|
67
|
+
"sphinx-autobuild>=2024.10.3",
|
|
68
|
+
"sphinx-lint>=1.0.0",
|
|
69
|
+
"sphinx-tabs>=3.4.7",
|
|
70
|
+
"sphinxcontrib-spelling>=8.0.2",
|
|
71
|
+
"textual-dev>=1.7.0",
|
|
72
|
+
"codespell>=2.4.0",
|
|
73
|
+
"myst-parser[linkify]>=4.0.0",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
[build-system]
|
|
77
|
+
requires = ["uv_build>=0.7.19,<0.13.0"]
|
|
78
|
+
build-backend = "uv_build"
|
|
79
|
+
|
|
80
|
+
[tool.uv.build-backend]
|
|
81
|
+
module-name = "exosphere"
|
|
82
|
+
|
|
83
|
+
[tool.pytest.ini_options]
|
|
84
|
+
testpaths = ["tests"]
|
|
85
|
+
|
|
86
|
+
[tool.codespell]
|
|
87
|
+
skip = "*.lock,*.woff,*.woff2,*.ttf,*.ttx,*.svg,*.png,*.gif,*.html,*.htm,docs/source/_static"
|
|
88
|
+
|
|
89
|
+
[tool.poe.tasks]
|
|
90
|
+
lint = "ruff check src tests scripts"
|
|
91
|
+
isort = "ruff check --select I --fix src tests scripts"
|
|
92
|
+
isort-check = "ruff check --select I src tests scripts --diff"
|
|
93
|
+
typecheck = "pyright src tests scripts"
|
|
94
|
+
ruff-format = "ruff format src tests scripts"
|
|
95
|
+
ruff-format-check = "ruff format --check src tests scripts --diff"
|
|
96
|
+
spellcheck = "codespell src tests scripts docs/source README.md"
|
|
97
|
+
spellcheck-fix = "codespell --write-changes src tests scripts docs/source README.md"
|
|
98
|
+
format = [
|
|
99
|
+
"ruff-format",
|
|
100
|
+
"isort",
|
|
101
|
+
]
|
|
102
|
+
format-check = [
|
|
103
|
+
"ruff-format-check",
|
|
104
|
+
"isort-check",
|
|
105
|
+
]
|
|
106
|
+
check = [
|
|
107
|
+
"format-check",
|
|
108
|
+
"lint",
|
|
109
|
+
"typecheck",
|
|
110
|
+
"spellcheck",
|
|
111
|
+
]
|
|
112
|
+
test = "pytest -v --ctrf .tests_report.json"
|
|
113
|
+
coverage = "pytest --cov-report term-missing --cov-report html --cov=src tests/"
|
|
114
|
+
docs-build = "sphinx-build -b html -W --keep-going docs/source docs/build/html"
|
|
115
|
+
docs-lint = "sphinx-lint docs/source"
|
|
116
|
+
docs-spellcheck = "sphinx-build -b spelling -W --keep-going docs/source docs/build"
|
|
117
|
+
docs-linkcheck = "sphinx-build -b linkcheck docs/source docs/build/linkcheck"
|
|
118
|
+
docs = [
|
|
119
|
+
"docs-lint",
|
|
120
|
+
"docs-spellcheck",
|
|
121
|
+
"docs-build",
|
|
122
|
+
]
|
|
123
|
+
docs-serve = "sphinx-autobuild -b html docs/source docs/build/html --port 8000 --open-browser"
|
|
124
|
+
docs-mkreports = "python scripts/generate_example_reports.py"
|
|
125
|
+
demo = "vhs scripts/demo.tape"
|
|
126
|
+
release-preflight = "python scripts/release_preflight.py"
|
|
127
|
+
release-check = [
|
|
128
|
+
"check",
|
|
129
|
+
"test",
|
|
130
|
+
"docs",
|
|
131
|
+
"release-preflight",
|
|
132
|
+
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "exosphere-cli"
|
|
3
|
-
version = "3.0.
|
|
3
|
+
version = "3.0.2"
|
|
4
4
|
description = "CLI/TUI driven patch reporting for remote Unix-like systems."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -36,7 +36,7 @@ dependencies = [
|
|
|
36
36
|
"jinja2>=3.1.6",
|
|
37
37
|
"packaging>=24.0",
|
|
38
38
|
"rich>=14.1.0",
|
|
39
|
-
"cyclopts>=4.16.1",
|
|
39
|
+
"cyclopts>=4.16.1,<5.0.0",
|
|
40
40
|
"filelock>=3.29.4",
|
|
41
41
|
"pydantic>=2.12",
|
|
42
42
|
]
|
|
@@ -65,7 +65,8 @@ dev = [
|
|
|
65
65
|
]
|
|
66
66
|
|
|
67
67
|
[project.urls]
|
|
68
|
-
homepage = "https://exosphere.
|
|
68
|
+
homepage = "https://exosphere.tools"
|
|
69
|
+
documentation = "https://docs.exosphere.tools"
|
|
69
70
|
repository = "https://github.com/mrdaemon/exosphere"
|
|
70
71
|
issues = "https://github.com/mrdaemon/exosphere/issues"
|
|
71
72
|
|
|
@@ -73,7 +74,7 @@ issues = "https://github.com/mrdaemon/exosphere/issues"
|
|
|
73
74
|
exosphere = "exosphere.main:main"
|
|
74
75
|
|
|
75
76
|
[build-system]
|
|
76
|
-
requires = ["uv_build>=0.7.19,<0.
|
|
77
|
+
requires = ["uv_build>=0.7.19,<0.13.0"]
|
|
77
78
|
build-backend = "uv_build"
|
|
78
79
|
|
|
79
80
|
[tool.uv.build-backend]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import importlib.metadata
|
|
2
|
+
|
|
3
|
+
from .config import Configuration
|
|
4
|
+
|
|
5
|
+
# Global Instances: configuration and GlobalState
|
|
6
|
+
# These are set at runtime and should be used as singletons
|
|
7
|
+
# to hold the global state and configuration.
|
|
8
|
+
|
|
9
|
+
app_config = Configuration() # Has default values out of the box
|
|
10
|
+
|
|
11
|
+
# Current software version, imported from pyproject metadata
|
|
12
|
+
__version__ = importlib.metadata.version("exosphere_cli")
|
|
13
|
+
|
|
14
|
+
# Current documentation url, from pyproject metadata
|
|
15
|
+
# Falls back to known good readthedocs URL to avoid disasters
|
|
16
|
+
_project_urls: list[str] = (
|
|
17
|
+
importlib.metadata.metadata("exosphere_cli").get_all("Project-URL") or []
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
docs_url = next(
|
|
21
|
+
(
|
|
22
|
+
entry.split(",", 1)[1].strip()
|
|
23
|
+
for entry in _project_urls
|
|
24
|
+
if entry.lower().startswith("documentation,")
|
|
25
|
+
),
|
|
26
|
+
"https://exosphere.readthedocs.io",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__all__ = ["__version__", "app_config", "docs_url"]
|
|
@@ -40,7 +40,7 @@ def show(
|
|
|
40
40
|
Displays the current configuration options, or the value of a specific option
|
|
41
41
|
if specified.
|
|
42
42
|
|
|
43
|
-
If
|
|
43
|
+
If ``--full`` is specified, it will show the entire configuration structure,
|
|
44
44
|
including the inventory, beyond just the "options" section.
|
|
45
45
|
|
|
46
46
|
Parameters
|
|
@@ -150,9 +150,9 @@ def diff(
|
|
|
150
150
|
file can exclusively contain the options you want to change.
|
|
151
151
|
|
|
152
152
|
This command allows you to see exactly what has been changed, optionally
|
|
153
|
-
in its context, using the
|
|
153
|
+
in its context, using the ``--full`` option.
|
|
154
154
|
|
|
155
|
-
For a full config dump, use the
|
|
155
|
+
For a full config dump, use the ``show`` command instead.
|
|
156
156
|
|
|
157
157
|
Parameters
|
|
158
158
|
----------
|
|
@@ -264,7 +264,7 @@ def edit(
|
|
|
264
264
|
|
|
265
265
|
try:
|
|
266
266
|
validate_config(target)
|
|
267
|
-
except
|
|
267
|
+
except (ValueError, OSError) as e:
|
|
268
268
|
err_console.print(f"[red]Configuration is invalid:[/red]\n{e}")
|
|
269
269
|
if Confirm.ask("Re-open editor to fix?", default=True):
|
|
270
270
|
continue
|
|
@@ -22,6 +22,7 @@ from exosphere.commands.utils import (
|
|
|
22
22
|
err_console,
|
|
23
23
|
save_inventory_state,
|
|
24
24
|
)
|
|
25
|
+
from exosphere.errors import DataRefreshError
|
|
25
26
|
from exosphere.objects import Host
|
|
26
27
|
|
|
27
28
|
# Simple spinner layout for long or pre-tasks
|
|
@@ -232,10 +233,10 @@ def discover(host: HostArg, /) -> int:
|
|
|
232
233
|
progress.add_task(f"Discovering platform for '{host.name}'", total=None)
|
|
233
234
|
try:
|
|
234
235
|
host.discover()
|
|
235
|
-
except
|
|
236
|
+
except DataRefreshError as e:
|
|
236
237
|
progress.console.print(
|
|
237
238
|
Panel.fit(
|
|
238
|
-
f"{
|
|
239
|
+
f"{e!s}",
|
|
239
240
|
title="[red]Error[/red]",
|
|
240
241
|
style="red",
|
|
241
242
|
title_align="left",
|
|
@@ -265,7 +266,7 @@ def refresh(
|
|
|
265
266
|
This command retrieves the host by name from the inventory
|
|
266
267
|
and refreshes its state and available updates.
|
|
267
268
|
|
|
268
|
-
If
|
|
269
|
+
If ``--sync`` is specified, the package repositories will also be
|
|
269
270
|
synchronized remotely.
|
|
270
271
|
|
|
271
272
|
Parameters
|
|
@@ -277,17 +278,17 @@ def refresh(
|
|
|
277
278
|
discover
|
|
278
279
|
Also refresh platform information
|
|
279
280
|
"""
|
|
280
|
-
with Progress(transient=True
|
|
281
|
+
with Progress(*SPINNER_ARGS, transient=True) as progress:
|
|
281
282
|
if discover:
|
|
282
283
|
task = progress.add_task(
|
|
283
284
|
f"Refreshing platform information for '{host.name}'", total=None
|
|
284
285
|
)
|
|
285
286
|
try:
|
|
286
287
|
host.discover()
|
|
287
|
-
except
|
|
288
|
+
except DataRefreshError as e:
|
|
288
289
|
progress.console.print(
|
|
289
290
|
Panel.fit(
|
|
290
|
-
f"{
|
|
291
|
+
f"{e!s}",
|
|
291
292
|
title="[red]Error[/red]",
|
|
292
293
|
style="red",
|
|
293
294
|
title_align="left",
|
|
@@ -304,10 +305,10 @@ def refresh(
|
|
|
304
305
|
)
|
|
305
306
|
try:
|
|
306
307
|
host.sync_repos()
|
|
307
|
-
except
|
|
308
|
+
except DataRefreshError as e:
|
|
308
309
|
progress.console.print(
|
|
309
310
|
Panel.fit(
|
|
310
|
-
f"{
|
|
311
|
+
f"{e!s}",
|
|
311
312
|
title="[red]Error[/red]",
|
|
312
313
|
style="red",
|
|
313
314
|
title_align="left",
|
|
@@ -321,10 +322,10 @@ def refresh(
|
|
|
321
322
|
task = progress.add_task(f"Refreshing updates for '{host.name}'", total=None)
|
|
322
323
|
try:
|
|
323
324
|
host.refresh_updates()
|
|
324
|
-
except
|
|
325
|
+
except DataRefreshError as e:
|
|
325
326
|
progress.console.print(
|
|
326
327
|
Panel.fit(
|
|
327
|
-
f"{
|
|
328
|
+
f"{e!s}",
|
|
328
329
|
title="[red]Error[/red]",
|
|
329
330
|
style="red",
|
|
330
331
|
title_align="left",
|
|
@@ -27,7 +27,7 @@ from exosphere.commands.utils import (
|
|
|
27
27
|
save_inventory_state,
|
|
28
28
|
)
|
|
29
29
|
from exosphere.inventory import FilterMode, Inventory, SortField
|
|
30
|
-
from exosphere.objects import HostOperation
|
|
30
|
+
from exosphere.objects import Host, HostOperation
|
|
31
31
|
|
|
32
32
|
# Constants for display
|
|
33
33
|
ERROR_STYLE = {
|
|
@@ -122,11 +122,11 @@ def refresh(
|
|
|
122
122
|
Connects to hosts in the inventory and retrieves pending package
|
|
123
123
|
updates.
|
|
124
124
|
|
|
125
|
-
If
|
|
125
|
+
If ``--discover`` is specified, the platform information (Operating
|
|
126
126
|
System flavor, version, package manager) will also be refreshed.
|
|
127
127
|
Also refreshes the online status in the process.
|
|
128
128
|
|
|
129
|
-
If
|
|
129
|
+
If ``--sync`` is specified, the package repositories will also be
|
|
130
130
|
synchronized remotely.
|
|
131
131
|
|
|
132
132
|
Synchronizing the package repositories involves invoking whatever mechanism
|
|
@@ -135,7 +135,7 @@ def refresh(
|
|
|
135
135
|
with a handful of slow hosts.
|
|
136
136
|
|
|
137
137
|
By default, only the progress bar is shown during the operation.
|
|
138
|
-
If
|
|
138
|
+
If ``--verbose`` is specified, the name and completion status of each host
|
|
139
139
|
will be shown in real time.
|
|
140
140
|
|
|
141
141
|
Parameters
|
|
@@ -274,7 +274,7 @@ def ping(*names: HostArg) -> int:
|
|
|
274
274
|
error_count += 1
|
|
275
275
|
if exc:
|
|
276
276
|
progress.console.print(
|
|
277
|
-
f" Host [bold]{host.name}[/bold]: [bold red]ERROR[/bold red] - {
|
|
277
|
+
f" Host [bold]{host.name}[/bold]: [bold red]ERROR[/bold red] - {exc!s}",
|
|
278
278
|
)
|
|
279
279
|
else:
|
|
280
280
|
progress.console.print(
|
|
@@ -342,18 +342,18 @@ def status(
|
|
|
342
342
|
online status and whether or not the data is stale.
|
|
343
343
|
|
|
344
344
|
Output can be filtered to show only hosts with pending updates
|
|
345
|
-
(
|
|
346
|
-
(
|
|
345
|
+
(``--updates-only``) or only those with pending security updates
|
|
346
|
+
(``--security-only``). These two filters are mutually exclusive.
|
|
347
347
|
|
|
348
|
-
Output can also be sorted by any column with
|
|
349
|
-
reversed with
|
|
348
|
+
Output can also be sorted by any column with ``--sort``, optionally
|
|
349
|
+
reversed with ``--reverse``. Sorting by 'version' groups hosts by
|
|
350
350
|
flavor first, since versions are not comparable across flavors.
|
|
351
351
|
|
|
352
352
|
When sorting by any column other than name, hosts with unknown
|
|
353
353
|
or unsupported values for that column will be grouped together at the
|
|
354
354
|
end.
|
|
355
355
|
|
|
356
|
-
Use
|
|
356
|
+
Use ``--full`` to include extra columns, such as the host description.
|
|
357
357
|
|
|
358
358
|
No matches when filtering will exit with code 3.
|
|
359
359
|
|
|
@@ -368,7 +368,7 @@ def status(
|
|
|
368
368
|
sort
|
|
369
369
|
Sort the table by the given column
|
|
370
370
|
reverse
|
|
371
|
-
Reverse the sort order (requires
|
|
371
|
+
Reverse the sort order (requires ``--sort``)
|
|
372
372
|
full
|
|
373
373
|
Show additional columns, including host descriptions
|
|
374
374
|
"""
|
|
@@ -420,12 +420,22 @@ def status(
|
|
|
420
420
|
caption_justify="right",
|
|
421
421
|
)
|
|
422
422
|
|
|
423
|
+
def get_platform_value(host: Host, value: str | None) -> str:
|
|
424
|
+
"""
|
|
425
|
+
Retrieve display value for host platform.
|
|
426
|
+
Will return appropriate placeholders as fallback.
|
|
427
|
+
"""
|
|
428
|
+
if value:
|
|
429
|
+
return value
|
|
430
|
+
elif not host.supported:
|
|
431
|
+
return "[dim](unsupported)[/dim]"
|
|
432
|
+
else:
|
|
433
|
+
return "[dim](undiscovered)[/dim]"
|
|
434
|
+
|
|
423
435
|
for host in hosts:
|
|
424
|
-
# Prepare some rendering data for suffixes
|
|
436
|
+
# Prepare some rendering data for suffixes
|
|
425
437
|
stale_suffix = " [dim]*[/dim]" if host.is_stale else ""
|
|
426
438
|
reboot_suffix = " [red]![/red]" if host.needs_reboot else ""
|
|
427
|
-
undiscovered_status = "[dim](undiscovered)[/dim]"
|
|
428
|
-
unsupported_status = "[dim](unsupported)[/dim]"
|
|
429
439
|
empty_placeholder = "[dim]—[/dim]"
|
|
430
440
|
|
|
431
441
|
# Prepare table row data
|
|
@@ -446,22 +456,12 @@ def status(
|
|
|
446
456
|
"[bold green]Online[/bold green]" if host.online else "[red]Offline[/red]"
|
|
447
457
|
) + reboot_suffix
|
|
448
458
|
|
|
449
|
-
# Helper function to get platform info with appropriate
|
|
450
|
-
# handling for unsupported and undiscovered hosts
|
|
451
|
-
def get_platform_info(value):
|
|
452
|
-
if value:
|
|
453
|
-
return value
|
|
454
|
-
elif not host.supported:
|
|
455
|
-
return unsupported_status
|
|
456
|
-
else:
|
|
457
|
-
return undiscovered_status
|
|
458
|
-
|
|
459
459
|
# Construct table row for host
|
|
460
460
|
row = [
|
|
461
461
|
host.name,
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
462
|
+
get_platform_value(host, host.os),
|
|
463
|
+
get_platform_value(host, host.flavor),
|
|
464
|
+
get_platform_value(host, host.version),
|
|
465
465
|
updates,
|
|
466
466
|
security_updates,
|
|
467
467
|
online_status,
|
|
@@ -537,7 +537,10 @@ def clear(
|
|
|
537
537
|
|
|
538
538
|
try:
|
|
539
539
|
inventory.clear_state()
|
|
540
|
-
except Exception as e:
|
|
540
|
+
except Exception as e: # noqa: BLE001
|
|
541
|
+
# clear_state wraps cache errors as RuntimeError, but also
|
|
542
|
+
# re-runs init_all() afterwards, which reopens the full config
|
|
543
|
+
# and host construction surfaces, so this is broad on purpose.
|
|
541
544
|
err_console.print(
|
|
542
545
|
Panel.fit(
|
|
543
546
|
f"[bold red]Error clearing inventory state:[/bold red] {e}",
|
|
@@ -102,8 +102,8 @@ def generate(
|
|
|
102
102
|
the report will include all hosts in the inventory.
|
|
103
103
|
|
|
104
104
|
It can further be narrowed to hosts with pending updates
|
|
105
|
-
(
|
|
106
|
-
(
|
|
105
|
+
(``--updates-only``) or pending security updates
|
|
106
|
+
(``--security-updates-only``), which are mutually exclusive.
|
|
107
107
|
|
|
108
108
|
Note: Undiscovered or unsupported hosts are excluded from the report.
|
|
109
109
|
|
|
@@ -120,7 +120,7 @@ def generate(
|
|
|
120
120
|
security_only
|
|
121
121
|
Only report security updates
|
|
122
122
|
tee
|
|
123
|
-
Also print report to stdout (requires
|
|
123
|
+
Also print report to stdout (requires ``--output``)
|
|
124
124
|
quiet
|
|
125
125
|
Suppress informational messages
|
|
126
126
|
navigation
|
|
@@ -200,7 +200,7 @@ def generate(
|
|
|
200
200
|
if output:
|
|
201
201
|
try:
|
|
202
202
|
output.write_text(content, encoding="utf-8")
|
|
203
|
-
except
|
|
203
|
+
except (OSError, UnicodeEncodeError) as e:
|
|
204
204
|
err_console.print(f"[red]Failed to write to {output}: {e}[/red]")
|
|
205
205
|
return 2 # Application error
|
|
206
206
|
|
|
@@ -241,14 +241,14 @@ def schema(
|
|
|
241
241
|
Show or write the JSON Schema for the current version of Exosphere
|
|
242
242
|
|
|
243
243
|
Emits the JSON Schema (draft-07) describing the structure produced
|
|
244
|
-
by
|
|
244
|
+
by ``report generate --format json``, for the currently running
|
|
245
245
|
version of Exosphere.
|
|
246
246
|
|
|
247
247
|
This allows anyone to easily get an overview of the structure,
|
|
248
248
|
validate and integrate, offline, without a local source tree or
|
|
249
249
|
access to the online documentation.
|
|
250
250
|
|
|
251
|
-
By default the schema is printed to stdout. Use
|
|
251
|
+
By default the schema is printed to stdout. Use ``--output`` to
|
|
252
252
|
write it to a file instead.
|
|
253
253
|
|
|
254
254
|
Parameters
|
|
@@ -267,7 +267,7 @@ def schema(
|
|
|
267
267
|
if output:
|
|
268
268
|
try:
|
|
269
269
|
output.write_text(content, encoding="utf-8")
|
|
270
|
-
except
|
|
270
|
+
except (OSError, UnicodeEncodeError) as e:
|
|
271
271
|
err_console.print(f"[red]Failed to write to {output}: {e}[/red]")
|
|
272
272
|
return 2 # Application error
|
|
273
273
|
|