redeploy 0.2.44__tar.gz → 0.2.45__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.44 → redeploy-0.2.45}/PKG-INFO +6 -6
- {redeploy-0.2.44 → redeploy-0.2.45}/README.md +5 -5
- {redeploy-0.2.44 → redeploy-0.2.45}/pyproject.toml +1 -1
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/__init__.py +1 -1
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/device_map.py +2 -1
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/hardware.py +2 -126
- redeploy-0.2.45/redeploy/config_apply/__init__.py +15 -0
- redeploy-0.2.45/redeploy/config_apply/applier.py +72 -0
- redeploy-0.2.45/redeploy/config_apply/loader.py +17 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/integrations/op3_bridge.py +45 -3
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy.egg-info/PKG-INFO +6 -6
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy.egg-info/SOURCES.txt +3 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/apply/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/apply/exceptions.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/apply/executor.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/apply/handlers.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/apply/progress.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/apply/rollback.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/apply/state.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/apply/state_apply.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/apply/utils/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/apply/utils/run_container_build.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/audit.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/blueprint/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/blueprint/extractor.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/blueprint/generators/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/blueprint/generators/docker_compose.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/blueprint/generators/migration.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/audit.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/blueprint.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/detect.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/devices.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/diagnose.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/diff.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/exec_.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/export.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/import_.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/init.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/inspect.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/patterns.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/plan_apply.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/plugin.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/probe.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/push.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/state.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/status.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/target.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/version/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/version/commands.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/version/helpers.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/version/monorepo.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/version/release.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/version/scanner.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/commands/workflow.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/core.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli/display.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/cli.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/data_sync.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/detect/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/detect/builtin/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/detect/detector.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/detect/hardware.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/detect/hardware_rules.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/detect/probes.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/detect/remote.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/detect/templates.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/detect/workflow.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/discovery.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/dsl/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/dsl/loader.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/dsl/parser.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/dsl_python/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/dsl_python/context.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/dsl_python/decorators.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/dsl_python/docker_steps.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/dsl_python/exceptions.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/dsl_python/runner.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/dsl_python/steps.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/fleet.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/hardware/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/hardware/config_txt.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/hardware/data/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/hardware/data/hyperpixel.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/hardware/data/official.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/hardware/data/waveshare.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/hardware/fixes.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/hardware/kiosk/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/hardware/kiosk/autostart.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/hardware/kiosk/browsers.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/hardware/kiosk/compositors.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/hardware/kiosk/output_profiles.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/hardware/panels.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/hardware/raspi_config.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/iac/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/iac/base.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/iac/docker_compose.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/iac/parsers/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/iac/parsers/compose.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/iac/registry.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/integrations/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/markpact/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/markpact/compiler.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/markpact/models.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/markpact/parser.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/models.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/observe.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/parse.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/patterns.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/plan/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/plan/planner.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/plugins/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/plugins/builtin/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/plugins/builtin/browser_reload.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/plugins/builtin/hardware_diagnostic.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/plugins/builtin/notify.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/plugins/builtin/process_control.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/plugins/builtin/systemd_reload.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/spec_loader.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/ssh.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/steps.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_audit.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_cli.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_data_sync.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_discovery.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_dsl.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_examples.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_executor.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_fleet.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_git_integration.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_iac.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_manifest.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_markpact_compiler.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_markpact_parser.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_models.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_observe.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_parse.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_patterns.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_planner_edge.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_plugins.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_probes.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_public_api.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_resume.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_spec_loader.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_ssh.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_templates.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_verify.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_version.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_version_changelog.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_version_cli.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_version_manifest.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_version_sources.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/tests/test_workflow.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/verify.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/version/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/version/bump.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/version/changelog.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/version/commits.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/version/diff.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/version/git_integration.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/version/git_transaction.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/version/manifest.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/version/sources/__init__.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/version/sources/base.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/version/sources/json_.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/version/sources/plain.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/version/sources/regex.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/version/sources/toml_.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/version/sources/yaml_.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/version/transaction.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy/version.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy.egg-info/dependency_links.txt +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy.egg-info/entry_points.txt +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy.egg-info/requires.txt +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/redeploy.egg-info/top_level.txt +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/setup.cfg +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/tests/test_hardware_config.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/tests/test_hardware_kiosk.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/tests/test_hardware_rules.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/tests/test_iac.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/tests/test_parse.py +0 -0
- {redeploy-0.2.44 → redeploy-0.2.45}/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.45
|
|
4
4
|
Summary: Infrastructure migration toolkit: detect → plan → apply
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -30,17 +30,17 @@ Requires-Dist: op3>=0.1.4; extra == "op3"
|
|
|
30
30
|
|
|
31
31
|
## AI Cost Tracking
|
|
32
32
|
|
|
33
|
-
     
|
|
34
|
+
  
|
|
35
35
|
|
|
36
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
37
|
-
- 👤 **Human dev:** ~$
|
|
36
|
+
- 🤖 **LLM usage:** $7.5000 (66 commits)
|
|
37
|
+
- 👤 **Human dev:** ~$2318 (23.2h @ $100/h, 30min dedup)
|
|
38
38
|
|
|
39
39
|
Generated on 2026-04-21 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|
|
43
|
-
    
|
|
44
44
|
|
|
45
45
|
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
|
|
46
46
|
|
|
@@ -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 (66 commits)
|
|
10
|
+
- 👤 **Human dev:** ~$2318 (23.2h @ $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.45"
|
|
11
11
|
|
|
12
12
|
# ── Core models ───────────────────────────────────────────────────────────────
|
|
13
13
|
from .models import ( # noqa: F401
|
|
@@ -113,7 +113,8 @@ def device_map_cmd(
|
|
|
113
113
|
|
|
114
114
|
# ── --apply-config ─────────────────────────────────────────────────────────
|
|
115
115
|
if apply_config:
|
|
116
|
-
|
|
116
|
+
from ...config_apply import apply_config_file
|
|
117
|
+
apply_config_file(apply_config, ssh_key=ssh_key, console=console)
|
|
117
118
|
return
|
|
118
119
|
|
|
119
120
|
# ── probe ─────────────────────────────────────────────────────────────────
|
|
@@ -254,131 +254,6 @@ def _apply_transform(console, p, hw, transform):
|
|
|
254
254
|
)
|
|
255
255
|
|
|
256
256
|
|
|
257
|
-
def _apply_config(console, p, config_path):
|
|
258
|
-
"""Apply hardware settings from a YAML/JSON config file to the remote host.
|
|
259
|
-
|
|
260
|
-
Reads drm_outputs[].transform entries and applies them via wlr-randr + kanshi.
|
|
261
|
-
Also reads backlights[].brightness / bl_power for backlight settings.
|
|
262
|
-
"""
|
|
263
|
-
import yaml
|
|
264
|
-
import json as _json
|
|
265
|
-
|
|
266
|
-
with open(config_path) as f:
|
|
267
|
-
raw = f.read()
|
|
268
|
-
|
|
269
|
-
try:
|
|
270
|
-
cfg = yaml.safe_load(raw)
|
|
271
|
-
except Exception:
|
|
272
|
-
cfg = _json.loads(raw)
|
|
273
|
-
|
|
274
|
-
applied = 0
|
|
275
|
-
|
|
276
|
-
# ── display transforms ────────────────────────────────────────────────────
|
|
277
|
-
for output in cfg.get("drm_outputs", []):
|
|
278
|
-
connector = output.get("connector", "")
|
|
279
|
-
transform = output.get("transform", "normal")
|
|
280
|
-
if not connector:
|
|
281
|
-
continue
|
|
282
|
-
if "DSI" not in connector and "HDMI" not in connector:
|
|
283
|
-
continue
|
|
284
|
-
|
|
285
|
-
# skip defaults we don't need to touch
|
|
286
|
-
if transform == "normal" and output.get("enabled") in (None, "enabled"):
|
|
287
|
-
console.print(f"[dim] skip {connector}: transform=normal (default)[/dim]")
|
|
288
|
-
continue
|
|
289
|
-
|
|
290
|
-
console.print(f"[cyan]→ {connector}: transform={transform}[/cyan]")
|
|
291
|
-
|
|
292
|
-
# Apply via wlr-randr
|
|
293
|
-
wlr_cmd = (
|
|
294
|
-
f"WAYLAND_DISPLAY=wayland-0 XDG_RUNTIME_DIR=/run/user/$(id -u) "
|
|
295
|
-
f"wlr-randr --output {connector} --transform {transform} 2>&1"
|
|
296
|
-
)
|
|
297
|
-
r = p.run(wlr_cmd)
|
|
298
|
-
if r.ok:
|
|
299
|
-
console.print(f"[green] ✓ wlr-randr applied[/green]")
|
|
300
|
-
else:
|
|
301
|
-
console.print(f"[yellow] ⚠ wlr-randr: {r.out.strip() or 'no output'}[/yellow]")
|
|
302
|
-
|
|
303
|
-
applied += 1
|
|
304
|
-
|
|
305
|
-
# ── persist all DSI transforms in kanshi ─────────────────────────────────
|
|
306
|
-
dsi_outputs = [
|
|
307
|
-
o for o in cfg.get("drm_outputs", [])
|
|
308
|
-
if "DSI" in o.get("connector", "")
|
|
309
|
-
]
|
|
310
|
-
if dsi_outputs:
|
|
311
|
-
_update_kanshi_from_cfg(console, p, dsi_outputs)
|
|
312
|
-
|
|
313
|
-
# ── backlight ─────────────────────────────────────────────────────────────
|
|
314
|
-
for bl in cfg.get("backlights", []):
|
|
315
|
-
name = bl.get("name", "")
|
|
316
|
-
brightness = bl.get("brightness")
|
|
317
|
-
bl_power = bl.get("bl_power")
|
|
318
|
-
if not name:
|
|
319
|
-
continue
|
|
320
|
-
if brightness is not None:
|
|
321
|
-
r = p.run(f"echo {brightness} | sudo tee /sys/class/backlight/{name}/brightness > /dev/null")
|
|
322
|
-
if r.ok:
|
|
323
|
-
console.print(f"[green] ✓ backlight {name}: brightness={brightness}[/green]")
|
|
324
|
-
if bl_power is not None:
|
|
325
|
-
r = p.run(f"echo {bl_power} | sudo tee /sys/class/backlight/{name}/bl_power > /dev/null")
|
|
326
|
-
if r.ok:
|
|
327
|
-
console.print(f"[green] ✓ backlight {name}: bl_power={bl_power}[/green]")
|
|
328
|
-
applied += 1
|
|
329
|
-
|
|
330
|
-
if applied == 0:
|
|
331
|
-
console.print("[yellow]Nothing to apply — no relevant settings found in config[/yellow]")
|
|
332
|
-
else:
|
|
333
|
-
console.print(f"\n[bold green]✓ Config applied from {config_path}[/bold green]")
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
def _update_kanshi_from_cfg(console, p, dsi_outputs: list):
|
|
337
|
-
"""Rebuild kanshi profile from drm_outputs list and write to ~/.config/kanshi/config."""
|
|
338
|
-
import re as _re
|
|
339
|
-
|
|
340
|
-
kanshi_cfg_path = "~/.config/kanshi/config"
|
|
341
|
-
read_r = p.run(f"cat {kanshi_cfg_path} 2>/dev/null")
|
|
342
|
-
current = read_r.out if read_r.ok else ""
|
|
343
|
-
|
|
344
|
-
for output in dsi_outputs:
|
|
345
|
-
connector = output.get("connector", "")
|
|
346
|
-
transform = output.get("transform", "normal")
|
|
347
|
-
|
|
348
|
-
output_line_pat = _re.compile(
|
|
349
|
-
rf'(\s*output\s+{_re.escape(connector)}\s+enable)(\s+transform\s+\S+)?'
|
|
350
|
-
)
|
|
351
|
-
if _re.search(output_line_pat, current):
|
|
352
|
-
if transform == "normal":
|
|
353
|
-
# Remove transform clause
|
|
354
|
-
current = _re.sub(output_line_pat, r'\1', current)
|
|
355
|
-
else:
|
|
356
|
-
current = _re.sub(output_line_pat, rf'\1 transform {transform}', current)
|
|
357
|
-
elif current.strip():
|
|
358
|
-
# output line exists but without "enable"
|
|
359
|
-
current = _re.sub(
|
|
360
|
-
rf'(\s*output\s+{_re.escape(connector)}\b)',
|
|
361
|
-
rf'\1 transform {transform}' if transform != "normal" else r'\1',
|
|
362
|
-
current,
|
|
363
|
-
)
|
|
364
|
-
else:
|
|
365
|
-
# No config at all — create minimal profile
|
|
366
|
-
current = (
|
|
367
|
-
f"profile waveshare-only {{\n"
|
|
368
|
-
f" output {connector} enable"
|
|
369
|
-
+ (f" transform {transform}" if transform != "normal" else "")
|
|
370
|
-
+ "\n"
|
|
371
|
-
+ " output HDMI-A-2 disable\n"
|
|
372
|
-
+ "}\n"
|
|
373
|
-
)
|
|
374
|
-
|
|
375
|
-
escaped = current.replace("'", "'\\''")
|
|
376
|
-
write_r = p.run(f"mkdir -p ~/.config/kanshi && printf '%s' '{escaped}' > {kanshi_cfg_path}")
|
|
377
|
-
if write_r.ok:
|
|
378
|
-
console.print(f"[green] ✓ kanshi config updated ({kanshi_cfg_path})[/green]")
|
|
379
|
-
p.run("pkill -SIGUSR1 kanshi 2>/dev/null || true")
|
|
380
|
-
|
|
381
|
-
|
|
382
257
|
def _execute_query(hw, query_expr, output_fmt):
|
|
383
258
|
"""Execute JMESPath query on hardware model and output result."""
|
|
384
259
|
import jmespath
|
|
@@ -551,7 +426,8 @@ def hardware(host, output_fmt, show_fix, apply_fix_component, panel_id, list_pan
|
|
|
551
426
|
|
|
552
427
|
# --apply-config: load YAML/JSON file and push settings to device
|
|
553
428
|
if apply_config is not None:
|
|
554
|
-
|
|
429
|
+
from ...config_apply import apply_config_file
|
|
430
|
+
apply_config_file(apply_config, host=host, ssh_key=ssh_key, console=console)
|
|
555
431
|
return
|
|
556
432
|
|
|
557
433
|
# --set-transform: apply rotation immediately and update kanshi config
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Shared config-apply layer for ``--apply-config`` across CLI commands.
|
|
2
|
+
|
|
3
|
+
Unifies the duplicated load-host-connect-apply logic found in
|
|
4
|
+
``hardware``, ``device-map``, and ``blueprint`` commands.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from .applier import apply_config_dict, apply_config_file
|
|
9
|
+
from .loader import load_config_file
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"apply_config_dict",
|
|
13
|
+
"apply_config_file",
|
|
14
|
+
"load_config_file",
|
|
15
|
+
]
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Apply a declarative config dict or file to a remote host."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
from ..apply.state_apply import apply_state, ApplyResult
|
|
9
|
+
from ..detect.remote import RemoteProbe
|
|
10
|
+
from .loader import load_config_file
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _normalize_hardware(data: dict) -> dict:
|
|
14
|
+
"""Flatten nested hardware configs for state_apply handlers.
|
|
15
|
+
|
|
16
|
+
DeviceMap and DeviceBlueprint nest hardware under a ``hardware`` key,
|
|
17
|
+
while ``redeploy hardware`` produces a flat file. This helper makes
|
|
18
|
+
both shapes palatable for :func:`apply_state`.
|
|
19
|
+
"""
|
|
20
|
+
if "drm_outputs" in data or "backlights" in data:
|
|
21
|
+
return data
|
|
22
|
+
hw = data.get("hardware")
|
|
23
|
+
if isinstance(hw, dict):
|
|
24
|
+
return hw
|
|
25
|
+
return data
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def apply_config_dict(data: dict, probe: RemoteProbe, console: Console) -> ApplyResult:
|
|
29
|
+
"""Apply *data* to the host behind *probe*.
|
|
30
|
+
|
|
31
|
+
Delegates to :func:`redeploy.apply.state_apply.apply_state` after
|
|
32
|
+
normalising nested ``hardware`` sections.
|
|
33
|
+
"""
|
|
34
|
+
normalized = _normalize_hardware(data)
|
|
35
|
+
return apply_state(normalized, probe, console)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def apply_config_file(
|
|
39
|
+
path: str | Path,
|
|
40
|
+
*,
|
|
41
|
+
host: str | None = None,
|
|
42
|
+
ssh_key: str | None = None,
|
|
43
|
+
console: Console | None = None,
|
|
44
|
+
) -> ApplyResult:
|
|
45
|
+
"""Load *path* and apply its hardware/infra settings to the remote host.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
path:
|
|
50
|
+
Local YAML/JSON file (DeviceMap, DeviceBlueprint, or flat hardware dump).
|
|
51
|
+
host:
|
|
52
|
+
Optional SSH target. Falls back to ``data["host"]``.
|
|
53
|
+
ssh_key:
|
|
54
|
+
Private-key path for the SSH connection.
|
|
55
|
+
console:
|
|
56
|
+
Rich console for progress output. Created automatically when ``None``.
|
|
57
|
+
|
|
58
|
+
Raises
|
|
59
|
+
------
|
|
60
|
+
ValueError
|
|
61
|
+
If no host can be determined (neither *host* nor ``data["host"]``).
|
|
62
|
+
"""
|
|
63
|
+
data = load_config_file(path)
|
|
64
|
+
_host = host or data.get("host")
|
|
65
|
+
if not _host:
|
|
66
|
+
raise ValueError("No host specified and no 'host' field in config file")
|
|
67
|
+
|
|
68
|
+
if console is None:
|
|
69
|
+
console = Console()
|
|
70
|
+
|
|
71
|
+
probe = RemoteProbe(_host, ssh_key=ssh_key)
|
|
72
|
+
return apply_config_dict(data, probe, console)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Load a declarative config file (YAML or JSON)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def load_config_file(path: str | Path) -> dict:
|
|
11
|
+
"""Read *path* and return a dict (YAML or JSON auto-detected)."""
|
|
12
|
+
path = Path(path)
|
|
13
|
+
raw = path.read_text(encoding="utf-8")
|
|
14
|
+
try:
|
|
15
|
+
return yaml.safe_load(raw) or {}
|
|
16
|
+
except Exception:
|
|
17
|
+
return json.loads(raw)
|
|
@@ -65,10 +65,52 @@ def snapshot_to_infra_state(snapshot: "Snapshot") -> "InfraState":
|
|
|
65
65
|
|
|
66
66
|
def snapshot_to_hardware_info(snapshot: "Snapshot") -> "HardwareInfo":
|
|
67
67
|
"""Convert opstree.Snapshot -> redeploy.HardwareInfo."""
|
|
68
|
-
from redeploy.models import HardwareInfo
|
|
68
|
+
from redeploy.models import HardwareInfo, DrmOutput, BacklightInfo
|
|
69
69
|
|
|
70
70
|
physical = snapshot.layer("physical.display")
|
|
71
|
-
|
|
71
|
+
os_kernel = snapshot.layer("os.kernel")
|
|
72
|
+
os_config = snapshot.layer("os.config")
|
|
73
|
+
|
|
74
|
+
if physical is None:
|
|
75
|
+
return HardwareInfo()
|
|
76
|
+
|
|
77
|
+
# Map DRM outputs (op3 field names differ slightly from redeploy)
|
|
78
|
+
drm_raw = physical.data.get("drm_outputs", [])
|
|
79
|
+
drm_outputs = []
|
|
80
|
+
for d in drm_raw:
|
|
81
|
+
drm_outputs.append(DrmOutput(
|
|
82
|
+
name=d.get("name", ""),
|
|
83
|
+
connector=d.get("connector", ""),
|
|
84
|
+
status=d.get("status", "unknown"),
|
|
85
|
+
enabled=d.get("enabled", "unknown"),
|
|
86
|
+
modes=d.get("modes", []),
|
|
87
|
+
transform=d.get("transform", "normal"),
|
|
88
|
+
position=d.get("position", "0,0"),
|
|
89
|
+
scale=d.get("scale", "1.0"),
|
|
90
|
+
edid_bytes=d.get("edid_bytes", 0),
|
|
91
|
+
power_state=d.get("dpms"),
|
|
92
|
+
sysfs_path=f"/sys/class/drm/{d.get('name', '')}",
|
|
93
|
+
))
|
|
94
|
+
|
|
95
|
+
# Map backlights
|
|
96
|
+
bl_raw = physical.data.get("backlights", [])
|
|
97
|
+
backlights = []
|
|
98
|
+
for b in bl_raw:
|
|
99
|
+
backlights.append(BacklightInfo(
|
|
100
|
+
name=b.get("name", ""),
|
|
101
|
+
brightness=b.get("brightness", 0),
|
|
102
|
+
max_brightness=b.get("max_brightness", 255),
|
|
103
|
+
bl_power=b.get("bl_power", 0),
|
|
104
|
+
display_name=b.get("display_name"),
|
|
105
|
+
sysfs_path=f"/sys/class/backlight/{b.get('name', '')}",
|
|
106
|
+
))
|
|
107
|
+
|
|
72
108
|
return HardwareInfo(
|
|
73
|
-
|
|
109
|
+
board=physical.data.get("board_model"),
|
|
110
|
+
kernel=os_kernel.data.get("version") if os_kernel else None,
|
|
111
|
+
config_txt=os_config.data.get("config_txt", "") if os_config else "",
|
|
112
|
+
config_txt_path=os_config.data.get("config_txt_path", "/boot/firmware/config.txt") if os_config else "/boot/firmware/config.txt",
|
|
113
|
+
drm_outputs=drm_outputs,
|
|
114
|
+
backlights=backlights,
|
|
115
|
+
framebuffers=physical.data.get("framebuffers", []),
|
|
74
116
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: redeploy
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.45
|
|
4
4
|
Summary: Infrastructure migration toolkit: detect → plan → apply
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -30,17 +30,17 @@ Requires-Dist: op3>=0.1.4; extra == "op3"
|
|
|
30
30
|
|
|
31
31
|
## AI Cost Tracking
|
|
32
32
|
|
|
33
|
-
     
|
|
34
|
+
  
|
|
35
35
|
|
|
36
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
37
|
-
- 👤 **Human dev:** ~$
|
|
36
|
+
- 🤖 **LLM usage:** $7.5000 (66 commits)
|
|
37
|
+
- 👤 **Human dev:** ~$2318 (23.2h @ $100/h, 30min dedup)
|
|
38
38
|
|
|
39
39
|
Generated on 2026-04-21 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|
|
43
|
-
    
|
|
44
44
|
|
|
45
45
|
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
|
|
46
46
|
|
|
@@ -68,6 +68,9 @@ redeploy/cli/commands/version/helpers.py
|
|
|
68
68
|
redeploy/cli/commands/version/monorepo.py
|
|
69
69
|
redeploy/cli/commands/version/release.py
|
|
70
70
|
redeploy/cli/commands/version/scanner.py
|
|
71
|
+
redeploy/config_apply/__init__.py
|
|
72
|
+
redeploy/config_apply/applier.py
|
|
73
|
+
redeploy/config_apply/loader.py
|
|
71
74
|
redeploy/detect/__init__.py
|
|
72
75
|
redeploy/detect/detector.py
|
|
73
76
|
redeploy/detect/hardware.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|