redeploy 0.2.32__tar.gz → 0.2.33__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.
- {redeploy-0.2.32 → redeploy-0.2.33}/PKG-INFO +6 -6
- {redeploy-0.2.32 → redeploy-0.2.33}/README.md +5 -5
- {redeploy-0.2.32 → redeploy-0.2.33}/pyproject.toml +1 -1
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/__init__.py +1 -1
- redeploy-0.2.33/redeploy/cli/commands/hardware.py +230 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/detect/__init__.py +2 -1
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/detect/detector.py +9 -1
- redeploy-0.2.33/redeploy/detect/hardware.py +442 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy.egg-info/PKG-INFO +6 -6
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy.egg-info/SOURCES.txt +2 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/apply/__init__.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/apply/exceptions.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/apply/executor.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/apply/handlers.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/apply/progress.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/apply/rollback.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/apply/state.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/audit.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/__init__.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/commands/__init__.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/commands/audit.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/commands/detect.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/commands/devices.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/commands/diagnose.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/commands/diff.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/commands/exec_.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/commands/export.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/commands/import_.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/commands/init.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/commands/inspect.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/commands/patterns.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/commands/plan_apply.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/commands/plugin.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/commands/probe.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/commands/state.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/commands/status.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/commands/target.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/commands/version.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/commands/workflow.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/core.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli/display.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/cli.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/data_sync.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/detect/probes.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/detect/remote.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/detect/templates.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/detect/workflow.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/discovery.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/dsl/__init__.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/dsl/loader.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/dsl/parser.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/dsl_python/__init__.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/dsl_python/context.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/dsl_python/decorators.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/dsl_python/docker_steps.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/dsl_python/exceptions.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/dsl_python/runner.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/dsl_python/steps.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/fleet.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/iac/__init__.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/iac/base.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/iac/docker_compose.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/iac/parsers/__init__.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/iac/parsers/compose.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/iac/registry.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/markpact/__init__.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/markpact/compiler.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/markpact/models.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/markpact/parser.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/models.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/observe.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/parse.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/patterns.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/plan/__init__.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/plan/planner.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/plugins/__init__.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/plugins/builtin/__init__.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/plugins/builtin/browser_reload.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/plugins/builtin/hardware_diagnostic.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/plugins/builtin/notify.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/plugins/builtin/process_control.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/plugins/builtin/systemd_reload.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/spec_loader.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/ssh.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/steps.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/__init__.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_audit.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_cli.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_data_sync.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_discovery.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_dsl.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_examples.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_executor.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_fleet.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_git_integration.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_iac.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_manifest.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_markpact_compiler.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_markpact_parser.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_models.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_observe.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_parse.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_patterns.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_planner_edge.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_plugins.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_probes.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_public_api.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_resume.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_spec_loader.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_ssh.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_templates.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_verify.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_version.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_version_changelog.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_version_cli.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_version_manifest.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_version_sources.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/tests/test_workflow.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/verify.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/version/__init__.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/version/bump.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/version/changelog.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/version/commits.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/version/diff.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/version/git_integration.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/version/git_transaction.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/version/manifest.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/version/sources/__init__.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/version/sources/base.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/version/sources/json_.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/version/sources/plain.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/version/sources/regex.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/version/sources/toml_.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/version/sources/yaml_.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/version/transaction.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy/version.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy.egg-info/dependency_links.txt +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy.egg-info/entry_points.txt +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy.egg-info/requires.txt +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/redeploy.egg-info/top_level.txt +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/setup.cfg +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/tests/test_iac.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/tests/test_parse.py +0 -0
- {redeploy-0.2.32 → redeploy-0.2.33}/tests/test_redeploy.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: redeploy
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.33
|
|
4
4
|
Summary: Infrastructure migration toolkit: detect → plan → apply
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -27,17 +27,17 @@ Requires-Dist: pfix>=0.1.60; extra == "dev"
|
|
|
27
27
|
|
|
28
28
|
## AI Cost Tracking
|
|
29
29
|
|
|
30
|
-
     
|
|
31
|
+
  
|
|
32
32
|
|
|
33
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
34
|
-
- 👤 **Human dev:** ~$
|
|
33
|
+
- 🤖 **LLM usage:** $7.5000 (53 commits)
|
|
34
|
+
- 👤 **Human dev:** ~$1758 (17.6h @ $100/h, 30min dedup)
|
|
35
35
|
|
|
36
36
|
Generated on 2026-04-21 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
40
|
-
    
|
|
41
41
|
|
|
42
42
|
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
|
|
43
43
|
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
## AI Cost Tracking
|
|
5
5
|
|
|
6
|
-
     
|
|
7
|
+
  
|
|
8
8
|
|
|
9
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
10
|
-
- 👤 **Human dev:** ~$
|
|
9
|
+
- 🤖 **LLM usage:** $7.5000 (53 commits)
|
|
10
|
+
- 👤 **Human dev:** ~$1758 (17.6h @ $100/h, 30min dedup)
|
|
11
11
|
|
|
12
12
|
Generated on 2026-04-21 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
    
|
|
17
17
|
|
|
18
18
|
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
|
|
19
19
|
|
|
@@ -7,7 +7,7 @@ Public API (stable from 0.2.0, semver guaranteed)::
|
|
|
7
7
|
|
|
8
8
|
Everything not listed in ``__all__`` is internal and may change without notice.
|
|
9
9
|
"""
|
|
10
|
-
__version__ = "0.2.
|
|
10
|
+
__version__ = "0.2.33"
|
|
11
11
|
|
|
12
12
|
# ── Core models ───────────────────────────────────────────────────────────────
|
|
13
13
|
from .models import ( # noqa: F401
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""hardware command — Probe and diagnose hardware on a remote host.
|
|
2
|
+
|
|
3
|
+
Detects DSI displays, DRM connectors, backlight, I2C buses,
|
|
4
|
+
GPIO, config.txt overlays and reports problems with fix suggestions.
|
|
5
|
+
|
|
6
|
+
Examples::
|
|
7
|
+
|
|
8
|
+
redeploy hardware pi@192.168.188.109
|
|
9
|
+
redeploy hardware pi@192.168.188.109 --format json
|
|
10
|
+
redeploy hardware pi@192.168.188.109 --fix # print all fix commands
|
|
11
|
+
redeploy hardware pi@192.168.188.109 --apply-fix dsi # run fix for component
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
import click
|
|
18
|
+
from rich.console import Console
|
|
19
|
+
from rich.panel import Panel
|
|
20
|
+
from rich.table import Table
|
|
21
|
+
from rich import box
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
_SEVERITY_COLOR = {
|
|
25
|
+
"info": "green",
|
|
26
|
+
"warning": "yellow",
|
|
27
|
+
"error": "red",
|
|
28
|
+
"critical": "bold red",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_SEVERITY_ICON = {
|
|
32
|
+
"info": "✓",
|
|
33
|
+
"warning": "⚠",
|
|
34
|
+
"error": "✗",
|
|
35
|
+
"critical": "✗✗",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@click.command()
|
|
40
|
+
@click.argument("host")
|
|
41
|
+
@click.option(
|
|
42
|
+
"--format", "output_fmt",
|
|
43
|
+
default="rich",
|
|
44
|
+
type=click.Choice(["rich", "yaml", "json"]),
|
|
45
|
+
help="Output format",
|
|
46
|
+
)
|
|
47
|
+
@click.option(
|
|
48
|
+
"--fix", "show_fix",
|
|
49
|
+
is_flag=True,
|
|
50
|
+
help="Print fix commands for all issues found",
|
|
51
|
+
)
|
|
52
|
+
@click.option(
|
|
53
|
+
"--apply-fix", "apply_fix_component",
|
|
54
|
+
default=None,
|
|
55
|
+
metavar="COMPONENT",
|
|
56
|
+
help=(
|
|
57
|
+
"Run the fix for a specific component via SSH "
|
|
58
|
+
"(e.g. 'backlight', 'overlay'). Requires sudo on target."
|
|
59
|
+
),
|
|
60
|
+
)
|
|
61
|
+
@click.option("--ssh-key", default=None, type=click.Path(), help="SSH private key path")
|
|
62
|
+
def hardware(host, output_fmt, show_fix, apply_fix_component, ssh_key):
|
|
63
|
+
"""Probe and diagnose hardware on a remote host.
|
|
64
|
+
|
|
65
|
+
Checks DSI display, DRM connectors, backlight controller, I2C buses,
|
|
66
|
+
config.txt overlays and Wayland compositor — then reports issues with
|
|
67
|
+
actionable fix suggestions.
|
|
68
|
+
|
|
69
|
+
\b
|
|
70
|
+
Examples:
|
|
71
|
+
redeploy hardware pi@192.168.188.109
|
|
72
|
+
redeploy hardware pi@192.168.188.109 --fix
|
|
73
|
+
redeploy hardware pi@192.168.188.109 --format yaml
|
|
74
|
+
"""
|
|
75
|
+
from ...detect.hardware import probe_hardware
|
|
76
|
+
from ...detect.remote import RemoteProbe
|
|
77
|
+
|
|
78
|
+
console = Console()
|
|
79
|
+
|
|
80
|
+
p = RemoteProbe(host, ssh_key=ssh_key) if ssh_key else RemoteProbe(host)
|
|
81
|
+
|
|
82
|
+
with console.status(f"[cyan]Probing hardware on {host}…[/cyan]"):
|
|
83
|
+
try:
|
|
84
|
+
hw = probe_hardware(p)
|
|
85
|
+
except ConnectionError as exc:
|
|
86
|
+
console.print(f"[red]✗ {exc}[/red]")
|
|
87
|
+
sys.exit(2)
|
|
88
|
+
except Exception as exc:
|
|
89
|
+
console.print(f"[red]✗ hardware probe failed: {exc}[/red]")
|
|
90
|
+
raise
|
|
91
|
+
|
|
92
|
+
if output_fmt == "yaml":
|
|
93
|
+
import yaml
|
|
94
|
+
click.echo(yaml.safe_dump(hw.model_dump(mode="json"), sort_keys=False))
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
if output_fmt == "json":
|
|
98
|
+
import json as _json
|
|
99
|
+
click.echo(_json.dumps(hw.model_dump(mode="json"), indent=2))
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
# ── rich output ─────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
console.print()
|
|
105
|
+
console.print(Panel(
|
|
106
|
+
f"[bold]hardware probe[/bold] [cyan]{host}[/cyan]\n"
|
|
107
|
+
f"Board: [white]{hw.board or 'unknown'}[/white] "
|
|
108
|
+
f"Kernel: [white]{hw.kernel or 'unknown'}[/white]",
|
|
109
|
+
box=box.ROUNDED,
|
|
110
|
+
expand=False,
|
|
111
|
+
))
|
|
112
|
+
|
|
113
|
+
# DRM Connectors
|
|
114
|
+
if hw.drm_outputs:
|
|
115
|
+
t = Table(title="DRM Connectors", show_header=True, box=box.SIMPLE, padding=(0, 1))
|
|
116
|
+
t.add_column("Connector")
|
|
117
|
+
t.add_column("Status")
|
|
118
|
+
t.add_column("Enabled")
|
|
119
|
+
t.add_column("Mode")
|
|
120
|
+
t.add_column("Transform")
|
|
121
|
+
for o in hw.drm_outputs:
|
|
122
|
+
status_color = "green" if o.status == "connected" else "red"
|
|
123
|
+
mode = o.modes[0] if o.modes else "—"
|
|
124
|
+
t.add_row(
|
|
125
|
+
o.connector,
|
|
126
|
+
f"[{status_color}]{o.status}[/{status_color}]",
|
|
127
|
+
o.enabled,
|
|
128
|
+
mode,
|
|
129
|
+
o.transform,
|
|
130
|
+
)
|
|
131
|
+
console.print(t)
|
|
132
|
+
|
|
133
|
+
# Backlights
|
|
134
|
+
if hw.backlights:
|
|
135
|
+
t = Table(title="Backlight", show_header=True, box=box.SIMPLE, padding=(0, 1))
|
|
136
|
+
t.add_column("Device")
|
|
137
|
+
t.add_column("Brightness")
|
|
138
|
+
t.add_column("Max")
|
|
139
|
+
t.add_column("Power")
|
|
140
|
+
t.add_column("Display")
|
|
141
|
+
for b in hw.backlights:
|
|
142
|
+
pw_color = "green" if b.bl_power == 0 else "red"
|
|
143
|
+
br_color = "green" if b.brightness > 0 else "red"
|
|
144
|
+
t.add_row(
|
|
145
|
+
b.name,
|
|
146
|
+
f"[{br_color}]{b.brightness}[/{br_color}]",
|
|
147
|
+
str(b.max_brightness),
|
|
148
|
+
f"[{pw_color}]{'ON' if b.bl_power == 0 else 'OFF'}[/{pw_color}]",
|
|
149
|
+
b.display_name or "—",
|
|
150
|
+
)
|
|
151
|
+
console.print(t)
|
|
152
|
+
|
|
153
|
+
# DSI config.txt overlays
|
|
154
|
+
if hw.dsi_overlays:
|
|
155
|
+
console.print(f"[dim]DSI overlays:[/dim] {', '.join(hw.dsi_overlays)}")
|
|
156
|
+
else:
|
|
157
|
+
console.print("[red]DSI overlays: none found[/red]")
|
|
158
|
+
|
|
159
|
+
# I2C buses
|
|
160
|
+
if hw.i2c_buses:
|
|
161
|
+
bus_summary = ", ".join(
|
|
162
|
+
f"i2c-{b.bus}" + (f" [{','.join(b.devices)}]" if b.devices else "")
|
|
163
|
+
for b in hw.i2c_buses
|
|
164
|
+
)
|
|
165
|
+
console.print(f"[dim]I2C buses:[/dim] {bus_summary}")
|
|
166
|
+
|
|
167
|
+
console.print()
|
|
168
|
+
|
|
169
|
+
# Diagnostics
|
|
170
|
+
if not hw.diagnostics:
|
|
171
|
+
console.print("[green]✓ No hardware issues detected[/green]")
|
|
172
|
+
else:
|
|
173
|
+
t = Table(title="Diagnostics", show_header=True, box=box.SIMPLE, padding=(0, 1))
|
|
174
|
+
t.add_column("", width=3)
|
|
175
|
+
t.add_column("Component", style="bold")
|
|
176
|
+
t.add_column("Message")
|
|
177
|
+
|
|
178
|
+
for d in hw.diagnostics:
|
|
179
|
+
color = _SEVERITY_COLOR.get(d.severity, "white")
|
|
180
|
+
icon = _SEVERITY_ICON.get(d.severity, "?")
|
|
181
|
+
t.add_row(
|
|
182
|
+
f"[{color}]{icon}[/{color}]",
|
|
183
|
+
f"[{color}]{d.component}[/{color}]",
|
|
184
|
+
d.message,
|
|
185
|
+
)
|
|
186
|
+
console.print(t)
|
|
187
|
+
|
|
188
|
+
# Fix hints
|
|
189
|
+
fixable = [d for d in hw.diagnostics if d.fix]
|
|
190
|
+
if fixable and show_fix:
|
|
191
|
+
console.print()
|
|
192
|
+
console.print("[bold]Fix suggestions:[/bold]")
|
|
193
|
+
for d in fixable:
|
|
194
|
+
color = _SEVERITY_COLOR.get(d.severity, "white")
|
|
195
|
+
console.print(
|
|
196
|
+
f"\n [{color}][{d.component}][/{color}] {d.message}\n"
|
|
197
|
+
+ "\n".join(f" [dim]{line}[/dim]" for line in (d.fix or "").splitlines())
|
|
198
|
+
)
|
|
199
|
+
elif fixable and not show_fix:
|
|
200
|
+
console.print(
|
|
201
|
+
f"\n[dim] {len(fixable)} fix suggestion(s) available — rerun with --fix[/dim]"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Apply fix
|
|
205
|
+
if apply_fix_component:
|
|
206
|
+
matching = [d for d in hw.diagnostics if d.component == apply_fix_component and d.fix]
|
|
207
|
+
if not matching:
|
|
208
|
+
console.print(f"[yellow]No fix available for component: {apply_fix_component}[/yellow]")
|
|
209
|
+
sys.exit(1)
|
|
210
|
+
|
|
211
|
+
for d in matching:
|
|
212
|
+
# Extract just the command lines (skip explanatory text)
|
|
213
|
+
fix_lines = [
|
|
214
|
+
line.strip()
|
|
215
|
+
for line in (d.fix or "").splitlines()
|
|
216
|
+
if line.strip() and not line.strip().startswith("#")
|
|
217
|
+
and ":" not in line[:20] # skip lines like "Check: ..."
|
|
218
|
+
]
|
|
219
|
+
for cmd in fix_lines:
|
|
220
|
+
console.print(f"[cyan]→ {cmd}[/cyan]")
|
|
221
|
+
result = p.run(cmd)
|
|
222
|
+
if result.ok:
|
|
223
|
+
console.print(f"[green] ✓ done[/green]")
|
|
224
|
+
else:
|
|
225
|
+
console.print(f"[red] ✗ failed: {result.err}[/red]")
|
|
226
|
+
|
|
227
|
+
# Exit code based on errors
|
|
228
|
+
errors = hw.errors
|
|
229
|
+
if errors:
|
|
230
|
+
sys.exit(1)
|
|
@@ -7,7 +7,8 @@ from typing import Optional
|
|
|
7
7
|
import yaml
|
|
8
8
|
from loguru import logger
|
|
9
9
|
|
|
10
|
-
from ..models import InfraState
|
|
10
|
+
from ..models import HardwareInfo, InfraState
|
|
11
|
+
from .hardware import probe_hardware
|
|
11
12
|
from .probes import (
|
|
12
13
|
detect_conflicts, detect_strategy, probe_docker_services,
|
|
13
14
|
probe_health, probe_iptables_dnat, probe_k3s_services,
|
|
@@ -94,3 +95,10 @@ class Detector:
|
|
|
94
95
|
data = state.model_dump(mode="json")
|
|
95
96
|
output.write_text(yaml.dump(data, default_flow_style=False, allow_unicode=True))
|
|
96
97
|
logger.info(f"InfraState saved to {output}")
|
|
98
|
+
|
|
99
|
+
def probe_hardware(self) -> HardwareInfo:
|
|
100
|
+
"""Probe hardware state (display, DSI, backlight, I2C, overlays)."""
|
|
101
|
+
if not self.probe.is_reachable():
|
|
102
|
+
raise ConnectionError(f"Host {self.host} is not reachable via SSH")
|
|
103
|
+
logger.info(f"Probing hardware on {self.host}")
|
|
104
|
+
return probe_hardware(self.probe)
|