redeploy 0.2.43__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.43 → redeploy-0.2.45}/PKG-INFO +8 -6
- {redeploy-0.2.43 → redeploy-0.2.45}/README.md +5 -5
- {redeploy-0.2.43 → redeploy-0.2.45}/pyproject.toml +2 -1
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/__init__.py +1 -1
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/device_map.py +39 -3
- {redeploy-0.2.43 → 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.45/redeploy/integrations/__init__.py +1 -0
- redeploy-0.2.45/redeploy/integrations/op3_bridge.py +116 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy.egg-info/PKG-INFO +8 -6
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy.egg-info/SOURCES.txt +5 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy.egg-info/requires.txt +3 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/apply/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/apply/exceptions.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/apply/executor.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/apply/handlers.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/apply/progress.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/apply/rollback.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/apply/state.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/apply/state_apply.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/apply/utils/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/apply/utils/run_container_build.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/audit.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/blueprint/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/blueprint/extractor.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/blueprint/generators/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/blueprint/generators/docker_compose.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/blueprint/generators/migration.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/audit.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/blueprint.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/detect.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/devices.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/diagnose.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/diff.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/exec_.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/export.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/import_.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/init.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/inspect.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/patterns.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/plan_apply.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/plugin.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/probe.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/push.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/state.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/status.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/target.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/version/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/version/commands.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/version/helpers.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/version/monorepo.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/version/release.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/version/scanner.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/workflow.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/core.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/display.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/data_sync.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/detect/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/detect/builtin/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/detect/detector.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/detect/hardware.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/detect/hardware_rules.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/detect/probes.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/detect/remote.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/detect/templates.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/detect/workflow.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/discovery.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/dsl/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/dsl/loader.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/dsl/parser.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/dsl_python/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/dsl_python/context.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/dsl_python/decorators.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/dsl_python/docker_steps.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/dsl_python/exceptions.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/dsl_python/runner.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/dsl_python/steps.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/fleet.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/config_txt.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/data/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/data/hyperpixel.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/data/official.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/data/waveshare.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/fixes.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/kiosk/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/kiosk/autostart.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/kiosk/browsers.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/kiosk/compositors.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/kiosk/output_profiles.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/panels.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/raspi_config.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/iac/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/iac/base.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/iac/docker_compose.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/iac/parsers/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/iac/parsers/compose.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/iac/registry.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/markpact/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/markpact/compiler.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/markpact/models.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/markpact/parser.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/models.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/observe.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/parse.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/patterns.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/plan/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/plan/planner.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/plugins/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/plugins/builtin/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/plugins/builtin/browser_reload.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/plugins/builtin/hardware_diagnostic.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/plugins/builtin/notify.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/plugins/builtin/process_control.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/plugins/builtin/systemd_reload.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/spec_loader.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/ssh.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/steps.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_audit.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_cli.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_data_sync.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_discovery.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_dsl.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_examples.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_executor.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_fleet.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_git_integration.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_iac.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_manifest.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_markpact_compiler.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_markpact_parser.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_models.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_observe.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_parse.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_patterns.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_planner_edge.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_plugins.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_probes.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_public_api.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_resume.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_spec_loader.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_ssh.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_templates.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_verify.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_version.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_version_changelog.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_version_cli.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_version_manifest.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_version_sources.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_workflow.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/verify.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/bump.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/changelog.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/commits.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/diff.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/git_integration.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/git_transaction.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/manifest.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/sources/__init__.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/sources/base.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/sources/json_.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/sources/plain.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/sources/regex.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/sources/toml_.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/sources/yaml_.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/transaction.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy.egg-info/dependency_links.txt +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy.egg-info/entry_points.txt +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/redeploy.egg-info/top_level.txt +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/setup.cfg +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/tests/test_hardware_config.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/tests/test_hardware_kiosk.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/tests/test_hardware_rules.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/tests/test_iac.py +0 -0
- {redeploy-0.2.43 → redeploy-0.2.45}/tests/test_parse.py +0 -0
- {redeploy-0.2.43 → 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
|
|
@@ -22,23 +22,25 @@ Requires-Dist: ruff>=0.4; extra == "dev"
|
|
|
22
22
|
Requires-Dist: goal>=2.1.0; extra == "dev"
|
|
23
23
|
Requires-Dist: costs>=0.1.20; extra == "dev"
|
|
24
24
|
Requires-Dist: pfix>=0.1.60; extra == "dev"
|
|
25
|
+
Provides-Extra: op3
|
|
26
|
+
Requires-Dist: op3>=0.1.4; extra == "op3"
|
|
25
27
|
|
|
26
28
|
# redeploy
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
## AI Cost Tracking
|
|
30
32
|
|
|
31
|
-
     
|
|
34
|
+
  
|
|
33
35
|
|
|
34
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
35
|
-
- 👤 **Human dev:** ~$
|
|
36
|
+
- 🤖 **LLM usage:** $7.5000 (66 commits)
|
|
37
|
+
- 👤 **Human dev:** ~$2318 (23.2h @ $100/h, 30min dedup)
|
|
36
38
|
|
|
37
39
|
Generated on 2026-04-21 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
38
40
|
|
|
39
41
|
---
|
|
40
42
|
|
|
41
|
-
    
|
|
42
44
|
|
|
43
45
|
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
|
|
44
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
|
|
|
@@ -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.45"
|
|
8
8
|
description = "Infrastructure migration toolkit: detect → plan → apply"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -32,6 +32,7 @@ dev = ["pytest>=8.0", "ruff>=0.4",
|
|
|
32
32
|
"costs>=0.1.20",
|
|
33
33
|
"pfix>=0.1.60",
|
|
34
34
|
]
|
|
35
|
+
op3 = ["op3>=0.1.4"]
|
|
35
36
|
|
|
36
37
|
[tool.ruff]
|
|
37
38
|
line-length = 100
|
|
@@ -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
|
|
@@ -50,10 +50,16 @@ from rich.table import Table
|
|
|
50
50
|
@click.option("--apply-config", "apply_config", default=None,
|
|
51
51
|
type=click.Path(exists=True, dir_okay=False),
|
|
52
52
|
help="Apply hardware/infra settings from YAML config file to the remote host")
|
|
53
|
+
@click.option(
|
|
54
|
+
"--query", "query_expr",
|
|
55
|
+
default=None,
|
|
56
|
+
metavar="EXPR",
|
|
57
|
+
help="Extract specific values using JMESPath query (e.g. 'hardware.drm_outputs[0].transform', 'host')",
|
|
58
|
+
)
|
|
53
59
|
@click.option("--ssh-key", default=None, type=click.Path(), help="SSH private key path")
|
|
54
60
|
def device_map_cmd(
|
|
55
61
|
host, name, tags, save, out_path, output_fmt,
|
|
56
|
-
no_infra, list_saved, show_file, diff_files, ssh_key, apply_config,
|
|
62
|
+
no_infra, list_saved, show_file, diff_files, ssh_key, apply_config, query_expr,
|
|
57
63
|
):
|
|
58
64
|
"""Generate a full standardized device snapshot (hardware + infra + diagnostics).
|
|
59
65
|
|
|
@@ -105,6 +111,12 @@ def device_map_cmd(
|
|
|
105
111
|
console.print("[red]✗ HOST required (or use --list / --show / --diff)[/red]")
|
|
106
112
|
sys.exit(1)
|
|
107
113
|
|
|
114
|
+
# ── --apply-config ─────────────────────────────────────────────────────────
|
|
115
|
+
if apply_config:
|
|
116
|
+
from ...config_apply import apply_config_file
|
|
117
|
+
apply_config_file(apply_config, ssh_key=ssh_key, console=console)
|
|
118
|
+
return
|
|
119
|
+
|
|
108
120
|
# ── probe ─────────────────────────────────────────────────────────────────
|
|
109
121
|
from ...detect.hardware import probe_hardware
|
|
110
122
|
from ...detect.remote import RemoteProbe
|
|
@@ -195,7 +207,7 @@ def _apply_config_from_file(console, config_path, ssh_key):
|
|
|
195
207
|
import yaml
|
|
196
208
|
import json as _json
|
|
197
209
|
|
|
198
|
-
from ...remote import
|
|
210
|
+
from ...detect.remote import RemoteProbe
|
|
199
211
|
|
|
200
212
|
with open(config_path) as f:
|
|
201
213
|
raw = f.read()
|
|
@@ -214,7 +226,7 @@ def _apply_config_from_file(console, config_path, ssh_key):
|
|
|
214
226
|
console.print(f"[cyan]→ Applying config from {config_path} to {host}[/cyan]")
|
|
215
227
|
|
|
216
228
|
# Connect to remote
|
|
217
|
-
p =
|
|
229
|
+
p = RemoteProbe(host, ssh_key=ssh_key)
|
|
218
230
|
|
|
219
231
|
applied = 0
|
|
220
232
|
|
|
@@ -321,6 +333,30 @@ def _update_kanshi_from_cfg(console, p, dsi_outputs: list):
|
|
|
321
333
|
p.run("pkill -SIGUSR1 kanshi 2>/dev/null || true")
|
|
322
334
|
|
|
323
335
|
|
|
336
|
+
def _execute_query_device_map(console, dm, query_expr, output_fmt):
|
|
337
|
+
"""Execute JMESPath query on DeviceMap model and output result."""
|
|
338
|
+
import jmespath
|
|
339
|
+
import json as _json
|
|
340
|
+
|
|
341
|
+
data = dm.model_dump(mode="json")
|
|
342
|
+
|
|
343
|
+
try:
|
|
344
|
+
result = jmespath.search(query_expr, data)
|
|
345
|
+
except jmespath.exceptions.JMESPathError as e:
|
|
346
|
+
console.print(f"[red]✗ JMESPath error:[/red] {e}")
|
|
347
|
+
sys.exit(1)
|
|
348
|
+
|
|
349
|
+
if result is None:
|
|
350
|
+
console.print("[dim]No match found for query[/dim]")
|
|
351
|
+
return
|
|
352
|
+
|
|
353
|
+
if output_fmt == "json":
|
|
354
|
+
click.echo(_json.dumps(result, indent=2, default=str))
|
|
355
|
+
else:
|
|
356
|
+
import yaml
|
|
357
|
+
click.echo(yaml.safe_dump(result, sort_keys=False, default_flow_style=False))
|
|
358
|
+
|
|
359
|
+
|
|
324
360
|
# ── rendering helpers ─────────────────────────────────────────────────────────
|
|
325
361
|
|
|
326
362
|
|
|
@@ -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)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Redeploy integrations."""
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Bridge between redeploy and op3.
|
|
2
|
+
|
|
3
|
+
All adapters go through here. This module isolates op3 usage so that redeploy
|
|
4
|
+
remains runnable when op3 is not installed.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from opstree.probes.context import SSHContext
|
|
13
|
+
from opstree.snapshot.model import Snapshot
|
|
14
|
+
from redeploy.models import InfraState, HardwareInfo
|
|
15
|
+
|
|
16
|
+
OP3_ENABLED_ENV = "REDEPLOY_USE_OP3"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def op3_enabled() -> bool:
|
|
20
|
+
"""Check whether the user wants to use op3."""
|
|
21
|
+
return os.environ.get(OP3_ENABLED_ENV, "0").lower() in ("1", "true", "yes")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def op3_available() -> bool:
|
|
25
|
+
"""Check whether op3 is installed."""
|
|
26
|
+
try:
|
|
27
|
+
import opstree # noqa: F401
|
|
28
|
+
return True
|
|
29
|
+
except ImportError:
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def should_use_op3() -> bool:
|
|
34
|
+
"""Use op3 only when both flag is on and library is available."""
|
|
35
|
+
return op3_enabled() and op3_available()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def make_op3_context_from_ssh_client(ssh_client) -> "SSHContext":
|
|
39
|
+
"""Convert redeploy.SshClient -> opstree.SSHContext."""
|
|
40
|
+
from opstree.probes.context import SSHContext
|
|
41
|
+
|
|
42
|
+
return SSHContext(
|
|
43
|
+
target=ssh_client.target,
|
|
44
|
+
host=ssh_client.host,
|
|
45
|
+
user=ssh_client.user,
|
|
46
|
+
ssh_key=ssh_client.ssh_key,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def snapshot_to_infra_state(snapshot: "Snapshot") -> "InfraState":
|
|
51
|
+
"""Convert opstree.Snapshot -> redeploy.InfraState (backward compat)."""
|
|
52
|
+
from redeploy.models import InfraState
|
|
53
|
+
|
|
54
|
+
runtime_data = snapshot.layer("runtime.container")
|
|
55
|
+
service_data = snapshot.layer("service.containers")
|
|
56
|
+
endpoint_data = snapshot.layer("endpoint.http")
|
|
57
|
+
|
|
58
|
+
# TODO: full mapping once layer schemas stabilise
|
|
59
|
+
return InfraState(
|
|
60
|
+
runtime=runtime_data.data if runtime_data else {},
|
|
61
|
+
services=service_data.data if service_data else {},
|
|
62
|
+
endpoints=endpoint_data.data if endpoint_data else {},
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def snapshot_to_hardware_info(snapshot: "Snapshot") -> "HardwareInfo":
|
|
67
|
+
"""Convert opstree.Snapshot -> redeploy.HardwareInfo."""
|
|
68
|
+
from redeploy.models import HardwareInfo, DrmOutput, BacklightInfo
|
|
69
|
+
|
|
70
|
+
physical = snapshot.layer("physical.display")
|
|
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
|
+
|
|
108
|
+
return HardwareInfo(
|
|
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", []),
|
|
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
|
|
@@ -22,23 +22,25 @@ Requires-Dist: ruff>=0.4; extra == "dev"
|
|
|
22
22
|
Requires-Dist: goal>=2.1.0; extra == "dev"
|
|
23
23
|
Requires-Dist: costs>=0.1.20; extra == "dev"
|
|
24
24
|
Requires-Dist: pfix>=0.1.60; extra == "dev"
|
|
25
|
+
Provides-Extra: op3
|
|
26
|
+
Requires-Dist: op3>=0.1.4; extra == "op3"
|
|
25
27
|
|
|
26
28
|
# redeploy
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
## AI Cost Tracking
|
|
30
32
|
|
|
31
|
-
     
|
|
34
|
+
  
|
|
33
35
|
|
|
34
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
35
|
-
- 👤 **Human dev:** ~$
|
|
36
|
+
- 🤖 **LLM usage:** $7.5000 (66 commits)
|
|
37
|
+
- 👤 **Human dev:** ~$2318 (23.2h @ $100/h, 30min dedup)
|
|
36
38
|
|
|
37
39
|
Generated on 2026-04-21 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
38
40
|
|
|
39
41
|
---
|
|
40
42
|
|
|
41
|
-
    
|
|
42
44
|
|
|
43
45
|
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
|
|
44
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
|
|
@@ -107,6 +110,8 @@ redeploy/iac/docker_compose.py
|
|
|
107
110
|
redeploy/iac/registry.py
|
|
108
111
|
redeploy/iac/parsers/__init__.py
|
|
109
112
|
redeploy/iac/parsers/compose.py
|
|
113
|
+
redeploy/integrations/__init__.py
|
|
114
|
+
redeploy/integrations/op3_bridge.py
|
|
110
115
|
redeploy/markpact/__init__.py
|
|
111
116
|
redeploy/markpact/compiler.py
|
|
112
117
|
redeploy/markpact/models.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
|