redeploy 0.2.76__tar.gz → 0.2.78__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.76 → redeploy-0.2.78}/PKG-INFO +5 -5
- {redeploy-0.2.76 → redeploy-0.2.78}/README.md +4 -4
- {redeploy-0.2.76 → redeploy-0.2.78}/pyproject.toml +1 -1
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/__init__.py +1 -1
- redeploy-0.2.78/redeploy/cli/commands/apply_cmd.py +51 -0
- redeploy-0.2.78/redeploy/cli/commands/device_map.py +78 -0
- redeploy-0.2.78/redeploy/cli/commands/device_map_actions.py +174 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/devices.py +6 -40
- redeploy-0.2.78/redeploy/cli/commands/devices_display.py +61 -0
- redeploy-0.2.78/redeploy/cli/commands/migrate_cmd.py +75 -0
- redeploy-0.2.78/redeploy/cli/commands/plan_apply.py +9 -0
- redeploy-0.2.78/redeploy/cli/commands/plan_apply_report.py +296 -0
- redeploy-0.2.78/redeploy/cli/commands/plan_apply_run.py +104 -0
- redeploy-0.2.78/redeploy/cli/commands/plan_apply_shared.py +235 -0
- redeploy-0.2.78/redeploy/cli/commands/plan_cmd.py +78 -0
- redeploy-0.2.78/redeploy/cli/commands/probe.py +91 -0
- redeploy-0.2.78/redeploy/cli/commands/probe_display.py +77 -0
- redeploy-0.2.78/redeploy/cli/commands/run_cmd.py +168 -0
- redeploy-0.2.78/redeploy/discovery/__init__.py +31 -0
- redeploy-0.2.78/redeploy/discovery/auto_probe.py +186 -0
- redeploy-0.2.78/redeploy/discovery/helpers.py +32 -0
- redeploy-0.2.78/redeploy/discovery/probe_parse.py +45 -0
- redeploy-0.2.78/redeploy/discovery/registry.py +64 -0
- redeploy-0.2.78/redeploy/discovery/scanners.py +254 -0
- redeploy-0.2.78/redeploy/discovery/ssh_credentials.py +111 -0
- redeploy-0.2.78/redeploy/discovery/types.py +38 -0
- redeploy-0.2.78/redeploy/tests/test_devices_display.py +33 -0
- redeploy-0.2.78/redeploy/tests/test_discovery_probe.py +45 -0
- redeploy-0.2.78/redeploy/tests/test_plan_apply_report.py +91 -0
- redeploy-0.2.78/redeploy/tests/test_plan_apply_shared.py +21 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy.egg-info/PKG-INFO +5 -5
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy.egg-info/SOURCES.txt +22 -1
- redeploy-0.2.76/redeploy/cli/commands/device_map.py +0 -299
- redeploy-0.2.76/redeploy/cli/commands/plan_apply.py +0 -1005
- redeploy-0.2.76/redeploy/cli/commands/probe.py +0 -144
- redeploy-0.2.76/redeploy/discovery.py +0 -789
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/analyze/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/analyze/checkers/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/analyze/checkers/base.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/analyze/checkers/binary.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/analyze/checkers/command_ref.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/analyze/checkers/compose.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/analyze/checkers/docker_build.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/analyze/checkers/env.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/analyze/checkers/path.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/analyze/checkers/reference.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/analyze/ignore.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/analyze/models.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/analyze/preflight_schema.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/analyze/spec_analyzer.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/apply/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/apply/exceptions.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/apply/executor.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/apply/handlers.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/apply/progress.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/apply/rollback.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/apply/state.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/apply/state_apply.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/apply/utils/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/apply/utils/run_container_build.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/audit/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/audit/auditor.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/audit/extractor.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/audit/models.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/audit/paths.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/audit/patterns.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/audit/probe.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/blueprint/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/blueprint/extractor.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/blueprint/generators/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/blueprint/generators/docker_compose.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/blueprint/generators/migration.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/blueprint/sources/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/blueprint/sources/compose.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/blueprint/sources/hardware.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/blueprint/sources/infra.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/blueprint/sources/migration.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/audit.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/blueprint.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/bump_fix.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/detect.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/device_map_renderers.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/diagnose.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/diff.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/exec_.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/export.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/gh_workflow.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/hardware.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/import_.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/init.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/inspect.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/lint.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/mcp_cmd.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/patterns.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/plugin.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/prompt_cmd.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/push.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/state.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/status.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/target.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/version/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/version/commands.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/version/helpers.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/version/monorepo.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/version/release.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/version/scanner.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/version/utils/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/version/utils/changelog_config.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/version/utils/git_config.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/commands/workflow.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/core.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/display.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli/query.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/cli.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/config_apply/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/config_apply/applier.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/config_apply/handlers/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/config_apply/handlers/display.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/config_apply/loader.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/data_sync.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/detect/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/detect/builtin/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/detect/detector.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/detect/hardware.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/detect/hardware_rules.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/detect/probes.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/detect/remote.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/detect/templates.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/detect/workflow.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/dsl/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/dsl/loader.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/dsl/parser.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/dsl_python/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/dsl_python/context.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/dsl_python/decorators.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/dsl_python/docker_steps.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/dsl_python/exceptions.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/dsl_python/runner.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/dsl_python/steps.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/fleet.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/hardware/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/hardware/config_txt.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/hardware/data/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/hardware/data/hyperpixel.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/hardware/data/official.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/hardware/data/waveshare.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/hardware/fixes.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/hardware/kiosk/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/hardware/kiosk/autostart.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/hardware/kiosk/browsers.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/hardware/kiosk/compositors.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/hardware/kiosk/output_profiles.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/hardware/panels.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/hardware/raspi_config.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/heal/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/heal/decider.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/heal/hint_provider.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/heal/log_writer.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/heal/loop_detector.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/heal/runner.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/heal.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/iac/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/iac/base.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/iac/config_hints.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/iac/docker_compose.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/iac/parsers/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/iac/parsers/compose.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/iac/registry.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/integrations/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/integrations/op3_bridge.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/markpact/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/markpact/compiler.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/markpact/models.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/markpact/parser.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/mcp_server.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/models/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/models/blueprint.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/models/devices.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/models/enums.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/models/hardware.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/models/infra.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/models/manifest.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/models/persisted.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/models/pipeline.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/models/plan.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/models/spec.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/observe.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/parse.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/patterns.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/plan/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/plan/planner.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/plugins/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/plugins/builtin/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/plugins/builtin/browser_reload.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/plugins/builtin/hardware_diagnostic.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/plugins/builtin/notify.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/plugins/builtin/process_control.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/plugins/builtin/systemd_reload.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/schema.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/spec_loader.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/ssh.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/steps/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/steps/docker.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/steps/generic.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/steps/hardware.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/steps/k3s.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/steps/kiosk.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/steps/podman.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/steps/process.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/steps/scm.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/steps/transfer.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_analyze.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_audit.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_cli.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_data_sync.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_discovery.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_dsl.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_examples.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_executor.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_fleet.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_git_integration.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_heal.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_heal_decider.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_hooks.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_iac.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_iac_compose_contract.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_iac_docker_compose_contract.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_manifest.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_markpact_compiler.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_markpact_parser.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_models.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_observe.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_parse.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_patterns.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_planner_edge.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_plugins.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_probes.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_public_api.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_resume.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_security_hardening.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_spec_loader.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_ssh.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_steps_registry.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_templates.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_verify.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_version.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_version_changelog.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_version_cli.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_version_manifest.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_version_sources.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/tests/test_workflow.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/verify.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/version/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/version/bump.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/version/changelog.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/version/commits.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/version/diff.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/version/git_integration.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/version/git_transaction.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/version/manifest.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/version/sources/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/version/sources/base.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/version/sources/json_.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/version/sources/plain.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/version/sources/regex.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/version/sources/toml_.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/version/sources/yaml_.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/version/transaction.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy/version.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy.egg-info/dependency_links.txt +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy.egg-info/entry_points.txt +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy.egg-info/requires.txt +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/redeploy.egg-info/top_level.txt +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/setup.cfg +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/tests/test_blueprint_sources.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/tests/test_blueprint_sources_compose.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/tests/test_cli_gh_workflow.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/tests/test_cli_query.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/tests/test_example_argocd_flux_plugin.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/tests/test_example_gitops_ci_plugin.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/tests/test_example_helm_kustomize_plugin.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/tests/test_hardware_config.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/tests/test_hardware_kiosk.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/tests/test_hardware_rules.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/tests/test_heal_decider.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/tests/test_heal_loop_detector.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/tests/test_iac.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/tests/test_iac_config_hints.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/tests/test_iac_plugin_loading.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/tests/test_import_plugin_template.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/tests/test_parse.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/tests/test_prompt_llm.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/tests/test_redeploy.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.78}/tests/test_steps_kiosk.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.78
|
|
4
4
|
Summary: Infrastructure migration toolkit: detect → plan → apply
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -33,17 +33,17 @@ Requires-Dist: mcp>=1.0; extra == "mcp"
|
|
|
33
33
|
|
|
34
34
|
## AI Cost Tracking
|
|
35
35
|
|
|
36
|
-
    
|
|
37
|
+
  
|
|
38
38
|
|
|
39
|
-
- 🤖 **LLM usage:** $
|
|
39
|
+
- 🤖 **LLM usage:** $6.3244 (100 commits)
|
|
40
40
|
- 👤 **Human dev:** ~$4198 (42.0h @ $100/h, 30min dedup)
|
|
41
41
|
|
|
42
42
|
Generated on 2026-05-20 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
43
43
|
|
|
44
44
|
---
|
|
45
45
|
|
|
46
|
-
    
|
|
47
47
|
|
|
48
48
|
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
|
|
49
49
|
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
## AI Cost Tracking
|
|
5
5
|
|
|
6
|
-
    
|
|
7
|
+
  
|
|
8
8
|
|
|
9
|
-
- 🤖 **LLM usage:** $
|
|
9
|
+
- 🤖 **LLM usage:** $6.3244 (100 commits)
|
|
10
10
|
- 👤 **Human dev:** ~$4198 (42.0h @ $100/h, 30min dedup)
|
|
11
11
|
|
|
12
12
|
Generated on 2026-05-20 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.78"
|
|
11
11
|
|
|
12
12
|
# ── Core models ───────────────────────────────────────────────────────────────
|
|
13
13
|
from .models import ( # noqa: F401
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""`redeploy apply` command."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command()
|
|
12
|
+
@click.option(
|
|
13
|
+
"--plan", "plan_file", default="migration-plan.yaml", show_default=True,
|
|
14
|
+
type=click.Path(exists=True), help="Migration plan file",
|
|
15
|
+
)
|
|
16
|
+
@click.option("--dry-run", is_flag=True, help="Show steps without executing")
|
|
17
|
+
@click.option("--step", default=None, help="Run only a specific step by ID")
|
|
18
|
+
@click.option("-o", "--output", default=None, type=click.Path(), help="Save results to file after apply")
|
|
19
|
+
@click.pass_context
|
|
20
|
+
def apply(ctx, plan_file, dry_run, step, output):
|
|
21
|
+
"""Execute a migration plan."""
|
|
22
|
+
from ...apply import Executor
|
|
23
|
+
|
|
24
|
+
console = Console()
|
|
25
|
+
executor = Executor.from_file(Path(plan_file))
|
|
26
|
+
|
|
27
|
+
if step:
|
|
28
|
+
matched = [s for s in executor.plan.steps if s.id == step]
|
|
29
|
+
if not matched:
|
|
30
|
+
console.print(f"[red]Step '{step}' not found in plan[/red]")
|
|
31
|
+
ids = ", ".join(s.id for s in executor.plan.steps)
|
|
32
|
+
console.print(f"Available: {ids}")
|
|
33
|
+
sys.exit(1)
|
|
34
|
+
executor.plan.steps = matched
|
|
35
|
+
|
|
36
|
+
executor.dry_run = dry_run
|
|
37
|
+
prefix = "[DRY RUN] " if dry_run else ""
|
|
38
|
+
console.print(
|
|
39
|
+
f"\n{prefix}[bold]Applying: {executor.plan.from_strategy.value}"
|
|
40
|
+
f" → {executor.plan.to_strategy.value}[/bold] "
|
|
41
|
+
f"({len(executor.plan.steps)} steps)"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
ok = executor.run()
|
|
45
|
+
console.print(f"\n{executor.summary()}")
|
|
46
|
+
|
|
47
|
+
if output:
|
|
48
|
+
executor.save_results(Path(output))
|
|
49
|
+
|
|
50
|
+
if not ok:
|
|
51
|
+
sys.exit(1)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""device-map command — Generate a full standardized device snapshot."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from .device_map_actions import (
|
|
11
|
+
emit_device_map,
|
|
12
|
+
execute_query_device_map,
|
|
13
|
+
print_device_map_diff,
|
|
14
|
+
print_saved_maps,
|
|
15
|
+
probe_device_map,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@click.command("device-map")
|
|
20
|
+
@click.argument("host", required=False, default=None)
|
|
21
|
+
@click.option("--name", default="", help="Human-friendly device label")
|
|
22
|
+
@click.option("--tag", "tags", multiple=True, help="Tag(s) to attach (repeatable)")
|
|
23
|
+
@click.option("--save", is_flag=True, help="Persist map to ~/.config/redeploy/device-maps/")
|
|
24
|
+
@click.option("--out", "out_path", default=None, type=click.Path(), help="Save to specific file")
|
|
25
|
+
@click.option("--format", "output_fmt", default="yaml", type=click.Choice(["yaml", "json"]))
|
|
26
|
+
@click.option("--no-infra", is_flag=True, help="Skip infra probe (faster — hardware only)")
|
|
27
|
+
@click.option("--list", "list_saved", is_flag=True, help="List saved device maps")
|
|
28
|
+
@click.option("--show", "show_file", default=None, type=click.Path(exists=True), help="Load and display a saved device-map file")
|
|
29
|
+
@click.option("--diff", "diff_files", nargs=2, type=click.Path(exists=True), help="Diff two saved device-map files")
|
|
30
|
+
@click.option("--apply-config", "apply_config", default=None, type=click.Path(exists=True, dir_okay=False))
|
|
31
|
+
@click.option("--query", "query_expr", default=None, metavar="EXPR", help="JMESPath query on the device map")
|
|
32
|
+
@click.option("--ssh-key", default=None, type=click.Path(), help="SSH private key path")
|
|
33
|
+
def device_map_cmd(
|
|
34
|
+
host, name, tags, save, out_path, output_fmt,
|
|
35
|
+
no_infra, list_saved, show_file, diff_files, ssh_key, apply_config, query_expr,
|
|
36
|
+
):
|
|
37
|
+
"""Generate a full standardized device snapshot (hardware + infra + diagnostics)."""
|
|
38
|
+
from ...models import DeviceMap
|
|
39
|
+
|
|
40
|
+
console = Console()
|
|
41
|
+
|
|
42
|
+
if list_saved:
|
|
43
|
+
print_saved_maps(console)
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
if show_file:
|
|
47
|
+
click.echo(DeviceMap.load(Path(show_file)).to_yaml())
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
if diff_files:
|
|
51
|
+
print_device_map_diff(console, Path(diff_files[0]), Path(diff_files[1]))
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
if not host:
|
|
55
|
+
console.print("[red]✗ HOST required (or use --list / --show / --diff)[/red]")
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
|
|
58
|
+
if apply_config:
|
|
59
|
+
from ...config_apply import apply_config_file
|
|
60
|
+
apply_config_file(apply_config, ssh_key=ssh_key, console=console)
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
device_map = probe_device_map(
|
|
64
|
+
console, host, name=name, tags=tags, ssh_key=ssh_key, no_infra=no_infra,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if query_expr:
|
|
68
|
+
execute_query_device_map(console, device_map, query_expr, output_fmt)
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
emit_device_map(device_map, output_fmt)
|
|
72
|
+
|
|
73
|
+
if save or out_path:
|
|
74
|
+
saved_path = device_map.save(Path(out_path) if out_path else None)
|
|
75
|
+
console.print(f"\n[green]✓ saved:[/green] {saved_path}")
|
|
76
|
+
|
|
77
|
+
if device_map.has_errors:
|
|
78
|
+
sys.exit(1)
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Non-Click logic for `redeploy device-map`."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from rich import box
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from ...models import DeviceMap
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def print_saved_maps(console: Console) -> None:
|
|
18
|
+
from ...models import DeviceMap
|
|
19
|
+
|
|
20
|
+
maps = DeviceMap.list_saved()
|
|
21
|
+
if not maps:
|
|
22
|
+
console.print("[dim]No saved device maps. Run:[/dim] redeploy device-map HOST --save")
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
table = Table(show_header=True, box=box.SIMPLE, padding=(0, 2))
|
|
26
|
+
table.add_column("File", style="bold")
|
|
27
|
+
table.add_column("Host", style="cyan")
|
|
28
|
+
table.add_column("Board")
|
|
29
|
+
table.add_column("Display")
|
|
30
|
+
table.add_column("Issues")
|
|
31
|
+
table.add_column("Scanned", style="dim")
|
|
32
|
+
|
|
33
|
+
for path in maps:
|
|
34
|
+
try:
|
|
35
|
+
dm = DeviceMap.load(path)
|
|
36
|
+
board = dm.hardware.board if dm.hardware else "?"
|
|
37
|
+
errors = sum(1 for i in dm.issues if i.get("severity") in ("error", "critical"))
|
|
38
|
+
warn = sum(1 for i in dm.issues if i.get("severity") == "warning")
|
|
39
|
+
issues_str = (
|
|
40
|
+
(f"[red]{errors}E[/red] " if errors else "")
|
|
41
|
+
+ (f"[yellow]{warn}W[/yellow]" if warn else "")
|
|
42
|
+
or "[green]OK[/green]"
|
|
43
|
+
)
|
|
44
|
+
table.add_row(
|
|
45
|
+
path.name,
|
|
46
|
+
dm.host,
|
|
47
|
+
board or "?",
|
|
48
|
+
dm.display_summary,
|
|
49
|
+
issues_str,
|
|
50
|
+
dm.scanned_at.strftime("%Y-%m-%d %H:%M"),
|
|
51
|
+
)
|
|
52
|
+
except Exception as exc:
|
|
53
|
+
table.add_row(path.name, "[red]parse error[/red]", str(exc)[:40], "", "", "")
|
|
54
|
+
|
|
55
|
+
console.print(table)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def print_device_map_diff(console: Console, path_a: Path, path_b: Path) -> None:
|
|
59
|
+
from ...models import DeviceMap
|
|
60
|
+
|
|
61
|
+
map_a = DeviceMap.load(path_a)
|
|
62
|
+
map_b = DeviceMap.load(path_b)
|
|
63
|
+
console.print(
|
|
64
|
+
f"\n[bold]diff[/bold] [cyan]{map_a.id}[/cyan] ({map_a.scanned_at.strftime('%m-%d %H:%M')})"
|
|
65
|
+
f" → [cyan]{map_b.id}[/cyan] ({map_b.scanned_at.strftime('%m-%d %H:%M')})\n"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def flatten(obj, prefix: str = "") -> dict:
|
|
69
|
+
out: dict = {}
|
|
70
|
+
if isinstance(obj, dict):
|
|
71
|
+
for key, value in obj.items():
|
|
72
|
+
out.update(flatten(value, f"{prefix}.{key}" if prefix else key))
|
|
73
|
+
elif isinstance(obj, list):
|
|
74
|
+
out[prefix] = str(obj)
|
|
75
|
+
else:
|
|
76
|
+
out[prefix] = str(obj)
|
|
77
|
+
return out
|
|
78
|
+
|
|
79
|
+
flat_a = flatten(map_a.model_dump(mode="json"))
|
|
80
|
+
flat_b = flatten(map_b.model_dump(mode="json"))
|
|
81
|
+
all_keys = sorted(set(flat_a) | set(flat_b))
|
|
82
|
+
changed = [(k, flat_a.get(k), flat_b.get(k)) for k in all_keys if flat_a.get(k) != flat_b.get(k)]
|
|
83
|
+
changed = [(k, old, new) for k, old, new in changed if not any(s in k for s in ("scanned_at", "id"))]
|
|
84
|
+
|
|
85
|
+
if not changed:
|
|
86
|
+
console.print("[green]✓ No differences[/green]")
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
table = Table(show_header=True, box=box.SIMPLE, padding=(0, 1))
|
|
90
|
+
table.add_column("Key")
|
|
91
|
+
table.add_column(f"A: {map_a.id[:20]}", style="red")
|
|
92
|
+
table.add_column(f"B: {map_b.id[:20]}", style="green")
|
|
93
|
+
for key, old, new in changed[:50]:
|
|
94
|
+
table.add_row(key, str(old or "—")[:60], str(new or "—")[:60])
|
|
95
|
+
console.print(table)
|
|
96
|
+
if len(changed) > 50:
|
|
97
|
+
console.print(f"[dim]... and {len(changed) - 50} more differences[/dim]")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def probe_device_map(
|
|
101
|
+
console: Console,
|
|
102
|
+
host: str,
|
|
103
|
+
*,
|
|
104
|
+
name: str,
|
|
105
|
+
tags: tuple[str, ...],
|
|
106
|
+
ssh_key: str | None,
|
|
107
|
+
no_infra: bool,
|
|
108
|
+
):
|
|
109
|
+
from ...detect import Detector
|
|
110
|
+
from ...detect.hardware import probe_hardware
|
|
111
|
+
from ...detect.remote import RemoteProbe
|
|
112
|
+
from ...models import DeviceMap
|
|
113
|
+
|
|
114
|
+
probe = RemoteProbe(host, ssh_key=ssh_key) if ssh_key else RemoteProbe(host)
|
|
115
|
+
|
|
116
|
+
with console.status(f"[cyan]Probing hardware on {host}…[/cyan]"):
|
|
117
|
+
try:
|
|
118
|
+
hw_info = probe_hardware(probe)
|
|
119
|
+
except ConnectionError as exc:
|
|
120
|
+
console.print(f"[red]✗ {exc}[/red]")
|
|
121
|
+
sys.exit(2)
|
|
122
|
+
|
|
123
|
+
infra_state = None
|
|
124
|
+
if not no_infra:
|
|
125
|
+
with console.status(f"[cyan]Probing infra on {host}…[/cyan]"):
|
|
126
|
+
try:
|
|
127
|
+
infra_state = Detector(host).run()
|
|
128
|
+
except Exception as exc:
|
|
129
|
+
console.print(f"[yellow]⚠ infra probe failed (continuing): {exc}[/yellow]")
|
|
130
|
+
|
|
131
|
+
issues: list[dict] = []
|
|
132
|
+
if hw_info:
|
|
133
|
+
for diag in hw_info.diagnostics:
|
|
134
|
+
issues.append({
|
|
135
|
+
"source": "hardware",
|
|
136
|
+
"component": diag.component,
|
|
137
|
+
"severity": diag.severity,
|
|
138
|
+
"message": diag.message,
|
|
139
|
+
"fix": diag.fix,
|
|
140
|
+
})
|
|
141
|
+
if infra_state:
|
|
142
|
+
for conflict in infra_state.conflicts:
|
|
143
|
+
issues.append({
|
|
144
|
+
"source": "infra",
|
|
145
|
+
"component": "service",
|
|
146
|
+
"severity": conflict.severity.value,
|
|
147
|
+
"message": f"{conflict.type}: {conflict.description}",
|
|
148
|
+
"fix": conflict.fix_hint,
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
return DeviceMap(
|
|
152
|
+
id=host,
|
|
153
|
+
host=host,
|
|
154
|
+
name=name,
|
|
155
|
+
tags=list(tags),
|
|
156
|
+
scanned_at=datetime.now(timezone.utc),
|
|
157
|
+
hardware=hw_info,
|
|
158
|
+
infra=infra_state,
|
|
159
|
+
issues=issues,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def execute_query_device_map(console: Console, dm: "DeviceMap", query_expr: str, output_fmt: str) -> None:
|
|
164
|
+
from ...cli.query import execute_query
|
|
165
|
+
execute_query(dm, query_expr, output_fmt, echo=console.print)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def emit_device_map(dm: "DeviceMap", output_fmt: str) -> None:
|
|
169
|
+
import click
|
|
170
|
+
if output_fmt == "json":
|
|
171
|
+
import json as json_mod
|
|
172
|
+
click.echo(json_mod.dumps(dm.model_dump(mode="json"), indent=2))
|
|
173
|
+
else:
|
|
174
|
+
click.echo(dm.to_yaml())
|
|
@@ -7,7 +7,6 @@ from pathlib import Path
|
|
|
7
7
|
|
|
8
8
|
import click
|
|
9
9
|
from rich.console import Console
|
|
10
|
-
from rich.table import Table
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
@click.command()
|
|
@@ -28,18 +27,13 @@ def devices(tag, strategy, rpi, reachable, as_json):
|
|
|
28
27
|
import json as _json
|
|
29
28
|
from ...models import DeviceRegistry
|
|
30
29
|
|
|
30
|
+
from .devices_display import filter_devices, render_devices_table
|
|
31
|
+
|
|
31
32
|
console = Console()
|
|
32
33
|
reg = DeviceRegistry.load()
|
|
33
|
-
devs =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
devs = [d for d in devs if tag in d.tags]
|
|
37
|
-
if strategy:
|
|
38
|
-
devs = [d for d in devs if d.strategy == strategy]
|
|
39
|
-
if rpi:
|
|
40
|
-
devs = [d for d in devs if "raspberry-pi" in d.tags]
|
|
41
|
-
if reachable:
|
|
42
|
-
devs = [d for d in devs if d.is_reachable]
|
|
34
|
+
devs = filter_devices(
|
|
35
|
+
reg.devices, tag=tag, strategy=strategy, rpi=rpi, reachable=reachable,
|
|
36
|
+
)
|
|
43
37
|
|
|
44
38
|
if as_json:
|
|
45
39
|
print(
|
|
@@ -55,35 +49,7 @@ def devices(tag, strategy, rpi, reachable, as_json):
|
|
|
55
49
|
console.print("[dim]No devices found. Run:[/dim] redeploy scan")
|
|
56
50
|
return
|
|
57
51
|
|
|
58
|
-
|
|
59
|
-
t.add_column("ID", style="bold")
|
|
60
|
-
t.add_column("Host")
|
|
61
|
-
t.add_column("Strategy", style="cyan")
|
|
62
|
-
t.add_column("App")
|
|
63
|
-
t.add_column("Tags", style="dim")
|
|
64
|
-
t.add_column("Last seen", style="dim")
|
|
65
|
-
t.add_column("SSH", style="dim")
|
|
66
|
-
|
|
67
|
-
for d in devs:
|
|
68
|
-
seen = d.last_seen.strftime("%m-%d %H:%M") if d.last_seen else "never"
|
|
69
|
-
ssh = "[green]✓[/green]" if d.last_ssh_ok else "[red]✗[/red]"
|
|
70
|
-
tags_str = ",".join(d.tags) or "—"
|
|
71
|
-
# Highlight raspberry-pi tag
|
|
72
|
-
if "raspberry-pi" in d.tags:
|
|
73
|
-
tags_str = tags_str.replace("raspberry-pi", "[bold magenta]raspberry-pi[/bold magenta]")
|
|
74
|
-
t.add_row(
|
|
75
|
-
d.id,
|
|
76
|
-
d.host,
|
|
77
|
-
d.strategy,
|
|
78
|
-
d.app or "—",
|
|
79
|
-
tags_str,
|
|
80
|
-
seen,
|
|
81
|
-
ssh,
|
|
82
|
-
)
|
|
83
|
-
console.print(t)
|
|
84
|
-
console.print(
|
|
85
|
-
f"\n [dim]{len(devs)} device(s) • registry: {DeviceRegistry.default_path()}[/dim]"
|
|
86
|
-
)
|
|
52
|
+
render_devices_table(console, devs)
|
|
87
53
|
|
|
88
54
|
|
|
89
55
|
@click.command()
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Rich table rendering for `redeploy devices`."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
|
|
7
|
+
from ...models import DeviceRegistry, KnownDevice
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def filter_devices(
|
|
11
|
+
devices: list[KnownDevice],
|
|
12
|
+
*,
|
|
13
|
+
tag: str | None,
|
|
14
|
+
strategy: str | None,
|
|
15
|
+
rpi: bool,
|
|
16
|
+
reachable: bool,
|
|
17
|
+
) -> list[KnownDevice]:
|
|
18
|
+
result = devices
|
|
19
|
+
if tag:
|
|
20
|
+
result = [d for d in result if tag in d.tags]
|
|
21
|
+
if strategy:
|
|
22
|
+
result = [d for d in result if d.strategy == strategy]
|
|
23
|
+
if rpi:
|
|
24
|
+
result = [d for d in result if "raspberry-pi" in d.tags]
|
|
25
|
+
if reachable:
|
|
26
|
+
result = [d for d in result if d.is_reachable]
|
|
27
|
+
return result
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def render_devices_table(console: Console, devices: list[KnownDevice]) -> None:
|
|
31
|
+
table = Table(show_header=True, box=None, padding=(0, 2))
|
|
32
|
+
table.add_column("ID", style="bold")
|
|
33
|
+
table.add_column("Host")
|
|
34
|
+
table.add_column("Strategy", style="cyan")
|
|
35
|
+
table.add_column("App")
|
|
36
|
+
table.add_column("Tags", style="dim")
|
|
37
|
+
table.add_column("Last seen", style="dim")
|
|
38
|
+
table.add_column("SSH", style="dim")
|
|
39
|
+
|
|
40
|
+
for device in devices:
|
|
41
|
+
seen = device.last_seen.strftime("%m-%d %H:%M") if device.last_seen else "never"
|
|
42
|
+
ssh = "[green]✓[/green]" if device.last_ssh_ok else "[red]✗[/red]"
|
|
43
|
+
tags_str = ",".join(device.tags) or "—"
|
|
44
|
+
if "raspberry-pi" in device.tags:
|
|
45
|
+
tags_str = tags_str.replace(
|
|
46
|
+
"raspberry-pi", "[bold magenta]raspberry-pi[/bold magenta]",
|
|
47
|
+
)
|
|
48
|
+
table.add_row(
|
|
49
|
+
device.id,
|
|
50
|
+
device.host,
|
|
51
|
+
device.strategy,
|
|
52
|
+
device.app or "—",
|
|
53
|
+
tags_str,
|
|
54
|
+
seen,
|
|
55
|
+
ssh,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
console.print(table)
|
|
59
|
+
console.print(
|
|
60
|
+
f"\n [dim]{len(devices)} device(s) • registry: {DeviceRegistry.default_path()}[/dim]"
|
|
61
|
+
)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""`redeploy migrate` command — detect → plan → apply."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command()
|
|
12
|
+
@click.option("--host", required=True, help="SSH host (user@ip) or 'local'")
|
|
13
|
+
@click.option("--app", default=None, show_default=True, help="Application name (default from redeploy.yaml)")
|
|
14
|
+
@click.option("--domain", default=None)
|
|
15
|
+
@click.option("--target", default=None, type=click.Path(), help="Target config YAML")
|
|
16
|
+
@click.option(
|
|
17
|
+
"--strategy", default="docker_full", show_default=True,
|
|
18
|
+
type=click.Choice(["docker_full", "podman_quadlet", "k3s", "native_kiosk", "systemd"]),
|
|
19
|
+
)
|
|
20
|
+
@click.option("--version", "target_version", default=None)
|
|
21
|
+
@click.option("--compose", multiple=True)
|
|
22
|
+
@click.option("--env-file", default=None)
|
|
23
|
+
@click.option("--dry-run", is_flag=True)
|
|
24
|
+
@click.option("--infra-out", default="infra.yaml", show_default=True, type=click.Path())
|
|
25
|
+
@click.option("--plan-out", default="migration-plan.yaml", show_default=True, type=click.Path())
|
|
26
|
+
@click.pass_context
|
|
27
|
+
def migrate(ctx, host, app, domain, target, strategy, target_version,
|
|
28
|
+
compose, env_file, dry_run, infra_out, plan_out):
|
|
29
|
+
"""Full pipeline: detect → plan → apply."""
|
|
30
|
+
from ...apply import Executor
|
|
31
|
+
from ...detect import Detector
|
|
32
|
+
from ...models import DeployStrategy, ProjectManifest
|
|
33
|
+
from ...plan import Planner
|
|
34
|
+
|
|
35
|
+
console = Console()
|
|
36
|
+
manifest = ProjectManifest.find_and_load(Path.cwd())
|
|
37
|
+
app = app or (manifest.app if manifest else "c2004")
|
|
38
|
+
domain = domain or (manifest.domain if manifest else None)
|
|
39
|
+
|
|
40
|
+
console.print("\n[bold]Step 1/3 — detect[/bold]")
|
|
41
|
+
detector = Detector(host=host, app=app, domain=domain)
|
|
42
|
+
state = detector.run()
|
|
43
|
+
detector.save(state, Path(infra_out))
|
|
44
|
+
console.print(
|
|
45
|
+
f" Strategy: {state.detected_strategy} "
|
|
46
|
+
f" Version: {state.current_version or '?'} "
|
|
47
|
+
f" Conflicts: {len(state.conflicts)}"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
console.print("\n[bold]Step 2/3 — plan[/bold]")
|
|
51
|
+
planner = Planner.from_files(Path(infra_out), Path(target) if target else None)
|
|
52
|
+
planner.target.strategy = DeployStrategy(strategy)
|
|
53
|
+
if domain:
|
|
54
|
+
planner.target.domain = domain
|
|
55
|
+
if target_version:
|
|
56
|
+
planner.target.verify_version = target_version
|
|
57
|
+
if compose:
|
|
58
|
+
planner.target.compose_files = list(compose)
|
|
59
|
+
if env_file:
|
|
60
|
+
planner.target.env_file = env_file
|
|
61
|
+
|
|
62
|
+
migration = planner.run()
|
|
63
|
+
planner.save(migration, Path(plan_out))
|
|
64
|
+
console.print(
|
|
65
|
+
f" Steps: {len(migration.steps)} Risk: {migration.risk.value} "
|
|
66
|
+
f"Downtime: {migration.estimated_downtime}"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
console.print(f"\n[bold]Step 3/3 — apply{' (dry-run)' if dry_run else ''}[/bold]")
|
|
70
|
+
executor = Executor(migration, dry_run=dry_run)
|
|
71
|
+
ok = executor.run()
|
|
72
|
+
console.print(f"\n{executor.summary()}")
|
|
73
|
+
|
|
74
|
+
if not ok:
|
|
75
|
+
sys.exit(1)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""plan, apply, migrate, run commands — re-exports for CLI registration."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from .apply_cmd import apply
|
|
5
|
+
from .migrate_cmd import migrate
|
|
6
|
+
from .plan_cmd import plan
|
|
7
|
+
from .run_cmd import run
|
|
8
|
+
|
|
9
|
+
__all__ = ["plan", "apply", "migrate", "run"]
|