redeploy 0.2.40__tar.gz → 0.2.42__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.40 → redeploy-0.2.42}/PKG-INFO +157 -6
- {redeploy-0.2.40 → redeploy-0.2.42}/README.md +155 -5
- {redeploy-0.2.40 → redeploy-0.2.42}/pyproject.toml +2 -1
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/__init__.py +1 -1
- redeploy-0.2.42/redeploy/apply/state_apply.py +222 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/__init__.py +2 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/blueprint.py +119 -6
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/device_map.py +165 -16
- redeploy-0.2.42/redeploy/cli/commands/diagnose.py +47 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/diff.py +1 -5
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/export.py +10 -10
- redeploy-0.2.42/redeploy/cli/commands/hardware.py +599 -0
- redeploy-0.2.42/redeploy/cli/commands/push.py +110 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/detect/hardware.py +71 -1
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/detect/hardware_rules.py +227 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/models.py +20 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy.egg-info/PKG-INFO +157 -6
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy.egg-info/SOURCES.txt +2 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy.egg-info/requires.txt +1 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/tests/test_hardware_rules.py +3 -3
- redeploy-0.2.40/redeploy/cli/commands/diagnose.py +0 -88
- redeploy-0.2.40/redeploy/cli/commands/hardware.py +0 -325
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/apply/__init__.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/apply/exceptions.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/apply/executor.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/apply/handlers.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/apply/progress.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/apply/rollback.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/apply/state.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/apply/utils/__init__.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/apply/utils/run_container_build.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/audit.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/blueprint/__init__.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/blueprint/extractor.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/blueprint/generators/__init__.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/blueprint/generators/docker_compose.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/blueprint/generators/migration.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/__init__.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/audit.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/detect.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/devices.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/exec_.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/import_.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/init.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/inspect.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/patterns.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/plan_apply.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/plugin.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/probe.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/state.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/status.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/target.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/version/__init__.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/version/commands.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/version/helpers.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/version/monorepo.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/version/release.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/version/scanner.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/commands/workflow.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/core.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli/display.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/cli.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/data_sync.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/detect/__init__.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/detect/builtin/__init__.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/detect/detector.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/detect/probes.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/detect/remote.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/detect/templates.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/detect/workflow.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/discovery.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/dsl/__init__.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/dsl/loader.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/dsl/parser.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/dsl_python/__init__.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/dsl_python/context.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/dsl_python/decorators.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/dsl_python/docker_steps.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/dsl_python/exceptions.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/dsl_python/runner.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/dsl_python/steps.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/fleet.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/hardware/__init__.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/hardware/config_txt.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/hardware/data/__init__.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/hardware/data/hyperpixel.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/hardware/data/official.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/hardware/data/waveshare.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/hardware/fixes.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/hardware/panels.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/hardware/raspi_config.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/iac/__init__.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/iac/base.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/iac/docker_compose.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/iac/parsers/__init__.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/iac/parsers/compose.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/iac/registry.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/markpact/__init__.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/markpact/compiler.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/markpact/models.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/markpact/parser.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/observe.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/parse.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/patterns.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/plan/__init__.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/plan/planner.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/plugins/__init__.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/plugins/builtin/__init__.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/plugins/builtin/browser_reload.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/plugins/builtin/hardware_diagnostic.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/plugins/builtin/notify.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/plugins/builtin/process_control.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/plugins/builtin/systemd_reload.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/spec_loader.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/ssh.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/steps.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/__init__.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_audit.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_cli.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_data_sync.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_discovery.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_dsl.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_examples.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_executor.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_fleet.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_git_integration.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_iac.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_manifest.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_markpact_compiler.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_markpact_parser.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_models.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_observe.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_parse.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_patterns.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_planner_edge.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_plugins.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_probes.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_public_api.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_resume.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_spec_loader.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_ssh.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_templates.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_verify.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_version.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_version_changelog.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_version_cli.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_version_manifest.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_version_sources.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/tests/test_workflow.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/verify.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/version/__init__.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/version/bump.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/version/changelog.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/version/commits.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/version/diff.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/version/git_integration.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/version/git_transaction.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/version/manifest.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/version/sources/__init__.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/version/sources/base.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/version/sources/json_.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/version/sources/plain.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/version/sources/regex.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/version/sources/toml_.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/version/sources/yaml_.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/version/transaction.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy/version.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy.egg-info/dependency_links.txt +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy.egg-info/entry_points.txt +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/redeploy.egg-info/top_level.txt +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/setup.cfg +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/tests/test_hardware_config.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/tests/test_iac.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/tests/test_parse.py +0 -0
- {redeploy-0.2.40 → redeploy-0.2.42}/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.42
|
|
4
4
|
Summary: Infrastructure migration toolkit: detect → plan → apply
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -12,6 +12,7 @@ Requires-Dist: loguru>=0.7
|
|
|
12
12
|
Requires-Dist: paramiko>=3.0
|
|
13
13
|
Requires-Dist: httpx>=0.25
|
|
14
14
|
Requires-Dist: rich>=13.0
|
|
15
|
+
Requires-Dist: jmespath>=1.0
|
|
15
16
|
Requires-Dist: goal>=2.1.0
|
|
16
17
|
Requires-Dist: costs>=0.1.20
|
|
17
18
|
Requires-Dist: pfix>=0.1.60
|
|
@@ -27,17 +28,17 @@ Requires-Dist: pfix>=0.1.60; extra == "dev"
|
|
|
27
28
|
|
|
28
29
|
## AI Cost Tracking
|
|
29
30
|
|
|
30
|
-
     
|
|
32
|
+
  
|
|
32
33
|
|
|
33
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
34
|
-
- 👤 **Human dev:** ~$
|
|
34
|
+
- 🤖 **LLM usage:** $7.5000 (63 commits)
|
|
35
|
+
- 👤 **Human dev:** ~$2088 (20.9h @ $100/h, 30min dedup)
|
|
35
36
|
|
|
36
37
|
Generated on 2026-04-21 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
37
38
|
|
|
38
39
|
---
|
|
39
40
|
|
|
40
|
-
    
|
|
41
42
|
|
|
42
43
|
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
|
|
43
44
|
|
|
@@ -152,6 +153,72 @@ redeploy device-rm root@10.0.0.5
|
|
|
152
153
|
|
|
153
154
|
Registry is stored at `~/.config/redeploy/devices.yaml` (chmod 600 — safe for SSH key paths).
|
|
154
155
|
|
|
156
|
+
## Declarative config workflow
|
|
157
|
+
|
|
158
|
+
redeploy supports a declarative configuration workflow for hardware settings — scan to YAML, edit locally, and apply to device:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
# 1. Scan hardware state to YAML
|
|
162
|
+
redeploy hardware pi@192.168.188.109 > hardware.yaml
|
|
163
|
+
redeploy device-map pi@192.168.188.109 > device-map.yaml
|
|
164
|
+
|
|
165
|
+
# 2. Edit YAML locally (e.g., change display rotation)
|
|
166
|
+
# hardware.yaml:
|
|
167
|
+
# drm_outputs:
|
|
168
|
+
# - name: card0-DSI-2
|
|
169
|
+
# connector: DSI-2
|
|
170
|
+
# transform: '270' # ← edit this value
|
|
171
|
+
|
|
172
|
+
# 3. Apply config to remote device
|
|
173
|
+
redeploy hardware pi@192.168.188.109 --apply-config hardware.yaml
|
|
174
|
+
redeploy device-map pi@192.168.188.109 --apply-config device-map.yaml
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**What `--apply-config` does:**
|
|
178
|
+
- Applies display transforms via `wlr-randr` (Wayland compositor)
|
|
179
|
+
- Updates kanshi config (`~/.config/kanshi/config`) for persistent display rotation
|
|
180
|
+
- Sets backlight brightness and power state
|
|
181
|
+
- Supports both YAML and JSON config files
|
|
182
|
+
|
|
183
|
+
**Supported commands:**
|
|
184
|
+
- `redeploy hardware --apply-config FILE`
|
|
185
|
+
- `redeploy device-map --apply-config FILE`
|
|
186
|
+
- `redeploy blueprint show FILE --apply-config FILE`
|
|
187
|
+
|
|
188
|
+
## JMESPath query support
|
|
189
|
+
|
|
190
|
+
Extract specific values from YAML/JSON output using JMESPath query language (similar to XPath for XML):
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
# Simple path queries
|
|
194
|
+
redeploy hardware pi@192.168.188.109 --query "drm_outputs[0].transform"
|
|
195
|
+
redeploy hardware pi@192.168.188.109 --query "kernel"
|
|
196
|
+
redeploy device-map pi@192.168.188.109 --query "host"
|
|
197
|
+
|
|
198
|
+
# Filter queries
|
|
199
|
+
redeploy hardware pi@192.168.188.109 --query "backlights[?name==\`11-0045\`].brightness"
|
|
200
|
+
redeploy hardware pi@192.168.188.109 --query "drm_outputs[?connector==\`DSI-2\`].transform"
|
|
201
|
+
|
|
202
|
+
# From saved YAML files
|
|
203
|
+
redeploy blueprint show blueprint.yaml --query "hardware.drm_outputs[0].transform"
|
|
204
|
+
redeploy device-map --show device-map.yaml --query "tags"
|
|
205
|
+
|
|
206
|
+
# JSON output
|
|
207
|
+
redeploy hardware pi@192.168.188.109 --query "tags" --format json
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**JMESPath features:**
|
|
211
|
+
- **Simple paths:** `kernel`, `host`, `board`
|
|
212
|
+
- **Array indexing:** `drm_outputs[0].transform`
|
|
213
|
+
- **Filtering:** `[?name==\`11-0045\`]`
|
|
214
|
+
- **Projections:** `backlights[0].[name,brightness]`
|
|
215
|
+
- **Wildcards:** `drm_outputs[*].transform`
|
|
216
|
+
|
|
217
|
+
**Supported commands:**
|
|
218
|
+
- `redeploy hardware --query EXPR`
|
|
219
|
+
- `redeploy device-map --query EXPR`
|
|
220
|
+
- `redeploy blueprint show FILE --query EXPR`
|
|
221
|
+
|
|
155
222
|
## CLI reference
|
|
156
223
|
|
|
157
224
|
### `redeploy run SPEC [options]`
|
|
@@ -614,6 +681,90 @@ redeploy run examples/01-vps-version-bump/migration.yaml --plan-only
|
|
|
614
681
|
redeploy run examples/04-rpi-kiosk/migration.yaml --plan-only
|
|
615
682
|
```
|
|
616
683
|
|
|
684
|
+
## Hardware diagnostics commands
|
|
685
|
+
|
|
686
|
+
### `redeploy hardware HOST [options]`
|
|
687
|
+
|
|
688
|
+
Probe and diagnose hardware on a remote host (DSI display, DRM connectors, backlight, I2C, config.txt, Wayland compositor).
|
|
689
|
+
|
|
690
|
+
| Option | Description |
|
|
691
|
+
|--------|-------------|
|
|
692
|
+
| `--format [yaml|json]` | Output format (default: yaml) |
|
|
693
|
+
| `--fix` | Print fix commands for all issues found |
|
|
694
|
+
| `--apply-fix COMPONENT` | Run fix for specific component via SSH |
|
|
695
|
+
| `--panel PANEL_ID` | Specify panel ID explicitly |
|
|
696
|
+
| `--list-panels` | List available panel definitions |
|
|
697
|
+
| `--set-transform TRANSFORM` | Set display rotation for DSI output (normal, 90, 180, 270, flipped, etc.) |
|
|
698
|
+
| `--apply-config FILE` | Apply display settings from YAML/JSON config file |
|
|
699
|
+
| `--query EXPR` | Extract specific values using JMESPath query |
|
|
700
|
+
| `--ssh-key PATH` | SSH private key path |
|
|
701
|
+
|
|
702
|
+
```bash
|
|
703
|
+
redeploy hardware pi@192.168.188.109
|
|
704
|
+
redeploy hardware pi@192.168.188.109 --fix
|
|
705
|
+
redeploy hardware pi@192.168.188.109 --set-transform 270
|
|
706
|
+
redeploy hardware pi@192.168.188.109 --apply-config hardware.yaml
|
|
707
|
+
redeploy hardware pi@192.168.188.109 --query "drm_outputs[0].transform"
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
### `redeploy device-map HOST [options]`
|
|
711
|
+
|
|
712
|
+
Generate full device snapshot (hardware + infra + diagnostics).
|
|
713
|
+
|
|
714
|
+
| Option | Description |
|
|
715
|
+
|--------|-------------|
|
|
716
|
+
| `--name TEXT` | Human-friendly device label |
|
|
717
|
+
| `--tag TEXT` | Tag(s) to attach (repeatable) |
|
|
718
|
+
| `--save` | Persist map to `~/.config/redeploy/device-maps/` |
|
|
719
|
+
| `--out PATH` | Save to specific file |
|
|
720
|
+
| `--format [yaml|json]` | Output format (default: yaml) |
|
|
721
|
+
| `--no-infra` | Skip infra probe (hardware only) |
|
|
722
|
+
| `--list` | List saved device maps |
|
|
723
|
+
| `--show PATH` | Load and display saved device-map file |
|
|
724
|
+
| `--diff PATH...` | Diff two saved device-map files |
|
|
725
|
+
| `--apply-config FILE` | Apply hardware/infra settings from YAML config file |
|
|
726
|
+
| `--query EXPR` | Extract specific values using JMESPath query |
|
|
727
|
+
| `--ssh-key PATH` | SSH private key path |
|
|
728
|
+
|
|
729
|
+
```bash
|
|
730
|
+
redeploy device-map pi@192.168.188.109 --save --name "kiosk-lab"
|
|
731
|
+
redeploy device-map --list
|
|
732
|
+
redeploy device-map --show ~/.config/redeploy/device-maps/pi_at_192.168.188.109.yaml
|
|
733
|
+
redeploy device-map pi@192.168.188.109 --apply-config device-map.yaml
|
|
734
|
+
redeploy device-map pi@192.168.188.109 --query "hardware.drm_outputs[0].transform"
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
### `redeploy blueprint [command]`
|
|
738
|
+
|
|
739
|
+
Manage device blueprints (capture, show, list, twin, migrate).
|
|
740
|
+
|
|
741
|
+
#### `redeploy blueprint capture HOST [options]`
|
|
742
|
+
|
|
743
|
+
Capture device state as blueprint.
|
|
744
|
+
|
|
745
|
+
| Option | Description |
|
|
746
|
+
|--------|-------------|
|
|
747
|
+
| `--format [yaml|json]` | Output format (default: yaml) |
|
|
748
|
+
| `--save` | Persist to `~/.config/redeploy/blueprints/` |
|
|
749
|
+
| `--out PATH` | Save to specific file |
|
|
750
|
+
| `--ssh-key PATH` | SSH private key path |
|
|
751
|
+
|
|
752
|
+
#### `redeploy blueprint show FILE [options]`
|
|
753
|
+
|
|
754
|
+
Display saved blueprint.
|
|
755
|
+
|
|
756
|
+
| Option | Description |
|
|
757
|
+
|--------|-------------|
|
|
758
|
+
| `--format [yaml|json]` | Output format (default: yaml) |
|
|
759
|
+
| `--apply-config FILE` | Apply blueprint settings from YAML config file |
|
|
760
|
+
| `--query EXPR` | Extract specific values using JMESPath query |
|
|
761
|
+
|
|
762
|
+
```bash
|
|
763
|
+
redeploy blueprint capture pi@192.168.188.109 > blueprint.yaml
|
|
764
|
+
redeploy blueprint show blueprint.yaml --apply-config blueprint.yaml
|
|
765
|
+
redeploy blueprint show blueprint.yaml --query "hardware.drm_outputs[0].transform"
|
|
766
|
+
```
|
|
767
|
+
|
|
617
768
|
## License
|
|
618
769
|
|
|
619
770
|
Licensed under Apache-2.0.
|
|
@@ -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 (63 commits)
|
|
10
|
+
- 👤 **Human dev:** ~$2088 (20.9h @ $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
|
|
|
@@ -128,6 +128,72 @@ redeploy device-rm root@10.0.0.5
|
|
|
128
128
|
|
|
129
129
|
Registry is stored at `~/.config/redeploy/devices.yaml` (chmod 600 — safe for SSH key paths).
|
|
130
130
|
|
|
131
|
+
## Declarative config workflow
|
|
132
|
+
|
|
133
|
+
redeploy supports a declarative configuration workflow for hardware settings — scan to YAML, edit locally, and apply to device:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# 1. Scan hardware state to YAML
|
|
137
|
+
redeploy hardware pi@192.168.188.109 > hardware.yaml
|
|
138
|
+
redeploy device-map pi@192.168.188.109 > device-map.yaml
|
|
139
|
+
|
|
140
|
+
# 2. Edit YAML locally (e.g., change display rotation)
|
|
141
|
+
# hardware.yaml:
|
|
142
|
+
# drm_outputs:
|
|
143
|
+
# - name: card0-DSI-2
|
|
144
|
+
# connector: DSI-2
|
|
145
|
+
# transform: '270' # ← edit this value
|
|
146
|
+
|
|
147
|
+
# 3. Apply config to remote device
|
|
148
|
+
redeploy hardware pi@192.168.188.109 --apply-config hardware.yaml
|
|
149
|
+
redeploy device-map pi@192.168.188.109 --apply-config device-map.yaml
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**What `--apply-config` does:**
|
|
153
|
+
- Applies display transforms via `wlr-randr` (Wayland compositor)
|
|
154
|
+
- Updates kanshi config (`~/.config/kanshi/config`) for persistent display rotation
|
|
155
|
+
- Sets backlight brightness and power state
|
|
156
|
+
- Supports both YAML and JSON config files
|
|
157
|
+
|
|
158
|
+
**Supported commands:**
|
|
159
|
+
- `redeploy hardware --apply-config FILE`
|
|
160
|
+
- `redeploy device-map --apply-config FILE`
|
|
161
|
+
- `redeploy blueprint show FILE --apply-config FILE`
|
|
162
|
+
|
|
163
|
+
## JMESPath query support
|
|
164
|
+
|
|
165
|
+
Extract specific values from YAML/JSON output using JMESPath query language (similar to XPath for XML):
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# Simple path queries
|
|
169
|
+
redeploy hardware pi@192.168.188.109 --query "drm_outputs[0].transform"
|
|
170
|
+
redeploy hardware pi@192.168.188.109 --query "kernel"
|
|
171
|
+
redeploy device-map pi@192.168.188.109 --query "host"
|
|
172
|
+
|
|
173
|
+
# Filter queries
|
|
174
|
+
redeploy hardware pi@192.168.188.109 --query "backlights[?name==\`11-0045\`].brightness"
|
|
175
|
+
redeploy hardware pi@192.168.188.109 --query "drm_outputs[?connector==\`DSI-2\`].transform"
|
|
176
|
+
|
|
177
|
+
# From saved YAML files
|
|
178
|
+
redeploy blueprint show blueprint.yaml --query "hardware.drm_outputs[0].transform"
|
|
179
|
+
redeploy device-map --show device-map.yaml --query "tags"
|
|
180
|
+
|
|
181
|
+
# JSON output
|
|
182
|
+
redeploy hardware pi@192.168.188.109 --query "tags" --format json
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**JMESPath features:**
|
|
186
|
+
- **Simple paths:** `kernel`, `host`, `board`
|
|
187
|
+
- **Array indexing:** `drm_outputs[0].transform`
|
|
188
|
+
- **Filtering:** `[?name==\`11-0045\`]`
|
|
189
|
+
- **Projections:** `backlights[0].[name,brightness]`
|
|
190
|
+
- **Wildcards:** `drm_outputs[*].transform`
|
|
191
|
+
|
|
192
|
+
**Supported commands:**
|
|
193
|
+
- `redeploy hardware --query EXPR`
|
|
194
|
+
- `redeploy device-map --query EXPR`
|
|
195
|
+
- `redeploy blueprint show FILE --query EXPR`
|
|
196
|
+
|
|
131
197
|
## CLI reference
|
|
132
198
|
|
|
133
199
|
### `redeploy run SPEC [options]`
|
|
@@ -590,6 +656,90 @@ redeploy run examples/01-vps-version-bump/migration.yaml --plan-only
|
|
|
590
656
|
redeploy run examples/04-rpi-kiosk/migration.yaml --plan-only
|
|
591
657
|
```
|
|
592
658
|
|
|
659
|
+
## Hardware diagnostics commands
|
|
660
|
+
|
|
661
|
+
### `redeploy hardware HOST [options]`
|
|
662
|
+
|
|
663
|
+
Probe and diagnose hardware on a remote host (DSI display, DRM connectors, backlight, I2C, config.txt, Wayland compositor).
|
|
664
|
+
|
|
665
|
+
| Option | Description |
|
|
666
|
+
|--------|-------------|
|
|
667
|
+
| `--format [yaml|json]` | Output format (default: yaml) |
|
|
668
|
+
| `--fix` | Print fix commands for all issues found |
|
|
669
|
+
| `--apply-fix COMPONENT` | Run fix for specific component via SSH |
|
|
670
|
+
| `--panel PANEL_ID` | Specify panel ID explicitly |
|
|
671
|
+
| `--list-panels` | List available panel definitions |
|
|
672
|
+
| `--set-transform TRANSFORM` | Set display rotation for DSI output (normal, 90, 180, 270, flipped, etc.) |
|
|
673
|
+
| `--apply-config FILE` | Apply display settings from YAML/JSON config file |
|
|
674
|
+
| `--query EXPR` | Extract specific values using JMESPath query |
|
|
675
|
+
| `--ssh-key PATH` | SSH private key path |
|
|
676
|
+
|
|
677
|
+
```bash
|
|
678
|
+
redeploy hardware pi@192.168.188.109
|
|
679
|
+
redeploy hardware pi@192.168.188.109 --fix
|
|
680
|
+
redeploy hardware pi@192.168.188.109 --set-transform 270
|
|
681
|
+
redeploy hardware pi@192.168.188.109 --apply-config hardware.yaml
|
|
682
|
+
redeploy hardware pi@192.168.188.109 --query "drm_outputs[0].transform"
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### `redeploy device-map HOST [options]`
|
|
686
|
+
|
|
687
|
+
Generate full device snapshot (hardware + infra + diagnostics).
|
|
688
|
+
|
|
689
|
+
| Option | Description |
|
|
690
|
+
|--------|-------------|
|
|
691
|
+
| `--name TEXT` | Human-friendly device label |
|
|
692
|
+
| `--tag TEXT` | Tag(s) to attach (repeatable) |
|
|
693
|
+
| `--save` | Persist map to `~/.config/redeploy/device-maps/` |
|
|
694
|
+
| `--out PATH` | Save to specific file |
|
|
695
|
+
| `--format [yaml|json]` | Output format (default: yaml) |
|
|
696
|
+
| `--no-infra` | Skip infra probe (hardware only) |
|
|
697
|
+
| `--list` | List saved device maps |
|
|
698
|
+
| `--show PATH` | Load and display saved device-map file |
|
|
699
|
+
| `--diff PATH...` | Diff two saved device-map files |
|
|
700
|
+
| `--apply-config FILE` | Apply hardware/infra settings from YAML config file |
|
|
701
|
+
| `--query EXPR` | Extract specific values using JMESPath query |
|
|
702
|
+
| `--ssh-key PATH` | SSH private key path |
|
|
703
|
+
|
|
704
|
+
```bash
|
|
705
|
+
redeploy device-map pi@192.168.188.109 --save --name "kiosk-lab"
|
|
706
|
+
redeploy device-map --list
|
|
707
|
+
redeploy device-map --show ~/.config/redeploy/device-maps/pi_at_192.168.188.109.yaml
|
|
708
|
+
redeploy device-map pi@192.168.188.109 --apply-config device-map.yaml
|
|
709
|
+
redeploy device-map pi@192.168.188.109 --query "hardware.drm_outputs[0].transform"
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
### `redeploy blueprint [command]`
|
|
713
|
+
|
|
714
|
+
Manage device blueprints (capture, show, list, twin, migrate).
|
|
715
|
+
|
|
716
|
+
#### `redeploy blueprint capture HOST [options]`
|
|
717
|
+
|
|
718
|
+
Capture device state as blueprint.
|
|
719
|
+
|
|
720
|
+
| Option | Description |
|
|
721
|
+
|--------|-------------|
|
|
722
|
+
| `--format [yaml|json]` | Output format (default: yaml) |
|
|
723
|
+
| `--save` | Persist to `~/.config/redeploy/blueprints/` |
|
|
724
|
+
| `--out PATH` | Save to specific file |
|
|
725
|
+
| `--ssh-key PATH` | SSH private key path |
|
|
726
|
+
|
|
727
|
+
#### `redeploy blueprint show FILE [options]`
|
|
728
|
+
|
|
729
|
+
Display saved blueprint.
|
|
730
|
+
|
|
731
|
+
| Option | Description |
|
|
732
|
+
|--------|-------------|
|
|
733
|
+
| `--format [yaml|json]` | Output format (default: yaml) |
|
|
734
|
+
| `--apply-config FILE` | Apply blueprint settings from YAML config file |
|
|
735
|
+
| `--query EXPR` | Extract specific values using JMESPath query |
|
|
736
|
+
|
|
737
|
+
```bash
|
|
738
|
+
redeploy blueprint capture pi@192.168.188.109 > blueprint.yaml
|
|
739
|
+
redeploy blueprint show blueprint.yaml --apply-config blueprint.yaml
|
|
740
|
+
redeploy blueprint show blueprint.yaml --query "hardware.drm_outputs[0].transform"
|
|
741
|
+
```
|
|
742
|
+
|
|
593
743
|
## License
|
|
594
744
|
|
|
595
745
|
Licensed under Apache-2.0.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "redeploy"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.42"
|
|
8
8
|
description = "Infrastructure migration toolkit: detect → plan → apply"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -17,6 +17,7 @@ dependencies = [
|
|
|
17
17
|
"paramiko>=3.0",
|
|
18
18
|
"httpx>=0.25",
|
|
19
19
|
"rich>=13.0",
|
|
20
|
+
"jmespath>=1.0",
|
|
20
21
|
"goal>=2.1.0",
|
|
21
22
|
"costs>=0.1.20",
|
|
22
23
|
"pfix>=0.1.60",
|
|
@@ -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.42"
|
|
11
11
|
|
|
12
12
|
# ── Core models ───────────────────────────────────────────────────────────────
|
|
13
13
|
from .models import ( # noqa: F401
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""Declarative state apply — push a desired-state YAML/JSON to a remote host.
|
|
2
|
+
|
|
3
|
+
Pattern:
|
|
4
|
+
redeploy hardware pi@host > device.yaml # scan
|
|
5
|
+
# edit device.yaml # declare desired state
|
|
6
|
+
redeploy push pi@host device.yaml # apply diff
|
|
7
|
+
|
|
8
|
+
The module is extensible: register a new :class:`StateHandler` for every new
|
|
9
|
+
file schema. Each handler says whether it can handle a given dict (``accept``)
|
|
10
|
+
and then applies the desired state (``apply``).
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
from abc import ABC, abstractmethod
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from rich.console import Console
|
|
20
|
+
|
|
21
|
+
from ..detect.remote import RemoteProbe
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ── handler base ──────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class ApplyResult:
|
|
29
|
+
handler: str
|
|
30
|
+
applied: list[str]
|
|
31
|
+
skipped: list[str]
|
|
32
|
+
errors: list[str]
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def ok(self) -> bool:
|
|
36
|
+
return not self.errors
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class StateHandler(ABC):
|
|
40
|
+
"""Base class for a declarative state applier."""
|
|
41
|
+
|
|
42
|
+
name: str = "base"
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def accept(self, data: dict) -> bool:
|
|
46
|
+
"""Return True if this handler knows how to apply *data*."""
|
|
47
|
+
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def apply(self, data: dict, p: RemoteProbe, console: Console) -> ApplyResult:
|
|
50
|
+
"""Apply *data* to the host behind *p*."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ── hardware handler ──────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class HardwareStateHandler(StateHandler):
|
|
57
|
+
"""Applies HardwareInfo-shaped YAML: display transforms, backlight, etc."""
|
|
58
|
+
|
|
59
|
+
name = "hardware"
|
|
60
|
+
|
|
61
|
+
def accept(self, data: dict) -> bool:
|
|
62
|
+
# HardwareInfo always has drm_outputs or backlights
|
|
63
|
+
return "drm_outputs" in data or "backlights" in data
|
|
64
|
+
|
|
65
|
+
def apply(self, data: dict, p: RemoteProbe, console: Console) -> ApplyResult:
|
|
66
|
+
applied: list[str] = []
|
|
67
|
+
skipped: list[str] = []
|
|
68
|
+
errors: list[str] = []
|
|
69
|
+
|
|
70
|
+
# ── display transforms ────────────────────────────────────────────────
|
|
71
|
+
for output in data.get("drm_outputs", []):
|
|
72
|
+
connector = output.get("connector", "")
|
|
73
|
+
transform = output.get("transform", "normal")
|
|
74
|
+
enabled = output.get("enabled", "enabled")
|
|
75
|
+
|
|
76
|
+
if not connector or ("DSI" not in connector and "HDMI" not in connector):
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
# Apply via wlr-randr
|
|
80
|
+
on_off = "--on" if enabled == "enabled" else "--off"
|
|
81
|
+
transform_arg = f"--transform {transform}" if transform != "normal" else ""
|
|
82
|
+
wlr_cmd = (
|
|
83
|
+
f"WAYLAND_DISPLAY=wayland-0 XDG_RUNTIME_DIR=/run/user/$(id -u) "
|
|
84
|
+
f"wlr-randr --output {connector} {on_off} {transform_arg} 2>&1"
|
|
85
|
+
).strip()
|
|
86
|
+
r = p.run(wlr_cmd)
|
|
87
|
+
if r.ok:
|
|
88
|
+
applied.append(f"display/{connector}: transform={transform} enabled={enabled}")
|
|
89
|
+
console.print(f"[green] ✓ {connector}: transform={transform}[/green]")
|
|
90
|
+
else:
|
|
91
|
+
msg = r.out.strip()
|
|
92
|
+
# wlr-randr unavailable (no compositor) — not fatal
|
|
93
|
+
if msg:
|
|
94
|
+
console.print(f"[yellow] ⚠ {connector} wlr-randr: {msg}[/yellow]")
|
|
95
|
+
skipped.append(f"display/{connector}: wlr-randr unavailable")
|
|
96
|
+
|
|
97
|
+
# ── persist DSI transform in kanshi ──────────────────────────────────
|
|
98
|
+
dsi_outputs = [o for o in data.get("drm_outputs", []) if "DSI" in o.get("connector", "")]
|
|
99
|
+
if dsi_outputs:
|
|
100
|
+
_update_kanshi(dsi_outputs, p, console, applied, errors)
|
|
101
|
+
|
|
102
|
+
# ── backlight ─────────────────────────────────────────────────────────
|
|
103
|
+
for bl in data.get("backlights", []):
|
|
104
|
+
name = bl.get("name", "")
|
|
105
|
+
if not name:
|
|
106
|
+
continue
|
|
107
|
+
brightness = bl.get("brightness")
|
|
108
|
+
bl_power = bl.get("bl_power")
|
|
109
|
+
if brightness is not None:
|
|
110
|
+
r = p.run(
|
|
111
|
+
f"echo {brightness} | sudo tee /sys/class/backlight/{name}/brightness > /dev/null"
|
|
112
|
+
)
|
|
113
|
+
if r.ok:
|
|
114
|
+
applied.append(f"backlight/{name}: brightness={brightness}")
|
|
115
|
+
console.print(f"[green] ✓ backlight/{name}: brightness={brightness}[/green]")
|
|
116
|
+
else:
|
|
117
|
+
errors.append(f"backlight/{name}: brightness set failed")
|
|
118
|
+
if bl_power is not None:
|
|
119
|
+
r = p.run(
|
|
120
|
+
f"echo {bl_power} | sudo tee /sys/class/backlight/{name}/bl_power > /dev/null"
|
|
121
|
+
)
|
|
122
|
+
if r.ok:
|
|
123
|
+
applied.append(f"backlight/{name}: bl_power={bl_power}")
|
|
124
|
+
console.print(f"[green] ✓ backlight/{name}: bl_power={bl_power}[/green]")
|
|
125
|
+
else:
|
|
126
|
+
errors.append(f"backlight/{name}: bl_power set failed")
|
|
127
|
+
|
|
128
|
+
return ApplyResult(handler=self.name, applied=applied, skipped=skipped, errors=errors)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _update_kanshi(dsi_outputs: list, p: RemoteProbe, console: Console,
|
|
132
|
+
applied: list, errors: list) -> None:
|
|
133
|
+
"""Rewrite ~/.config/kanshi/config from desired dsi_outputs state."""
|
|
134
|
+
kanshi_cfg_path = "~/.config/kanshi/config"
|
|
135
|
+
read_r = p.run(f"cat {kanshi_cfg_path} 2>/dev/null")
|
|
136
|
+
current = read_r.out if read_r.ok else ""
|
|
137
|
+
|
|
138
|
+
for output in dsi_outputs:
|
|
139
|
+
connector = output.get("connector", "")
|
|
140
|
+
transform = output.get("transform", "normal")
|
|
141
|
+
|
|
142
|
+
line_pat = re.compile(
|
|
143
|
+
rf'(\s*output\s+{re.escape(connector)}\s+enable)(\s+transform\s+\S+)?'
|
|
144
|
+
)
|
|
145
|
+
if re.search(line_pat, current):
|
|
146
|
+
if transform == "normal":
|
|
147
|
+
current = re.sub(line_pat, r'\1', current)
|
|
148
|
+
else:
|
|
149
|
+
current = re.sub(line_pat, rf'\1 transform {transform}', current)
|
|
150
|
+
elif current.strip():
|
|
151
|
+
current = re.sub(
|
|
152
|
+
rf'(\s*output\s+{re.escape(connector)}\b)',
|
|
153
|
+
(rf'\1 transform {transform}' if transform != "normal" else r'\1'),
|
|
154
|
+
current,
|
|
155
|
+
)
|
|
156
|
+
else:
|
|
157
|
+
current = (
|
|
158
|
+
f"profile waveshare-only {{\n"
|
|
159
|
+
f" output {connector} enable"
|
|
160
|
+
+ (f" transform {transform}" if transform != "normal" else "")
|
|
161
|
+
+ "\n output HDMI-A-2 disable\n}\n"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
escaped = current.replace("'", "'\\''")
|
|
165
|
+
write_r = p.run(
|
|
166
|
+
f"mkdir -p ~/.config/kanshi && printf '%s' '{escaped}' > {kanshi_cfg_path}"
|
|
167
|
+
)
|
|
168
|
+
if write_r.ok:
|
|
169
|
+
applied.append(f"kanshi: config updated")
|
|
170
|
+
console.print(f"[green] ✓ kanshi config saved ({kanshi_cfg_path})[/green]")
|
|
171
|
+
else:
|
|
172
|
+
errors.append("kanshi: config write failed")
|
|
173
|
+
|
|
174
|
+
# Reload kanshi if running — SIGUSR1 = reload profiles
|
|
175
|
+
p.run("pkill -SIGUSR1 kanshi 2>/dev/null || true")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# ── future handlers registered here ──────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class InfraStateHandler(StateHandler):
|
|
182
|
+
"""Placeholder — applies InfraState-shaped YAML (services, ports, etc.)."""
|
|
183
|
+
|
|
184
|
+
name = "infra"
|
|
185
|
+
|
|
186
|
+
def accept(self, data: dict) -> bool:
|
|
187
|
+
return "runtime" in data and "services" in data
|
|
188
|
+
|
|
189
|
+
def apply(self, data: dict, p: RemoteProbe, console: Console) -> ApplyResult:
|
|
190
|
+
# TODO: implement service-level desired state reconciliation
|
|
191
|
+
console.print("[yellow] infra state apply not yet implemented[/yellow]")
|
|
192
|
+
return ApplyResult(handler=self.name, applied=[], skipped=["not implemented"], errors=[])
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# ── registry ──────────────────────────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
_HANDLERS: list[StateHandler] = [
|
|
198
|
+
HardwareStateHandler(),
|
|
199
|
+
InfraStateHandler(),
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def detect_handler(data: dict) -> StateHandler | None:
|
|
204
|
+
"""Return the first handler that accepts *data*, or None."""
|
|
205
|
+
for h in _HANDLERS:
|
|
206
|
+
if h.accept(data):
|
|
207
|
+
return h
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def apply_state(data: dict, p: RemoteProbe, console: Console) -> ApplyResult:
|
|
212
|
+
"""Auto-detect file type and apply desired state."""
|
|
213
|
+
handler = detect_handler(data)
|
|
214
|
+
if handler is None:
|
|
215
|
+
return ApplyResult(
|
|
216
|
+
handler="unknown",
|
|
217
|
+
applied=[],
|
|
218
|
+
skipped=[],
|
|
219
|
+
errors=["No handler found for this file format"],
|
|
220
|
+
)
|
|
221
|
+
console.print(f"[dim]handler: {handler.name}[/dim]")
|
|
222
|
+
return handler.apply(data, p, console)
|
|
@@ -49,6 +49,7 @@ from .commands.exec_ import exec_cmd, exec_multi_cmd
|
|
|
49
49
|
from .commands.plan_apply import plan, apply, migrate, run
|
|
50
50
|
from .commands.state import state_cmd
|
|
51
51
|
from .commands.hardware import hardware
|
|
52
|
+
from .commands.push import push
|
|
52
53
|
from .commands.device_map import device_map_cmd
|
|
53
54
|
from .commands.blueprint import blueprint_cmd
|
|
54
55
|
from .commands.init import init
|
|
@@ -68,6 +69,7 @@ cli.add_command(exec_multi_cmd)
|
|
|
68
69
|
cli.add_command(export_cmd)
|
|
69
70
|
cli.add_command(import_cmd)
|
|
70
71
|
cli.add_command(hardware)
|
|
72
|
+
cli.add_command(push)
|
|
71
73
|
cli.add_command(device_map_cmd, name="device-map")
|
|
72
74
|
cli.add_command(blueprint_cmd, name="blueprint")
|
|
73
75
|
cli.add_command(init)
|