redeploy 0.2.56__tar.gz → 0.2.58__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.56 → redeploy-0.2.58}/PKG-INFO +6 -6
- {redeploy-0.2.56 → redeploy-0.2.58}/README.md +5 -5
- {redeploy-0.2.56 → redeploy-0.2.58}/pyproject.toml +1 -1
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/__init__.py +1 -1
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/iac/docker_compose.py +37 -4
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/iac/parsers/compose.py +9 -5
- redeploy-0.2.58/redeploy/models/blueprint.py +95 -0
- redeploy-0.2.58/redeploy/models/enums.py +69 -0
- redeploy-0.2.58/redeploy/models/hardware.py +117 -0
- redeploy-0.2.58/redeploy/models/infra.py +77 -0
- redeploy-0.2.58/redeploy/models/manifest.py +151 -0
- redeploy-0.2.58/redeploy/models/persisted.py +27 -0
- redeploy-0.2.58/redeploy/models/pipeline.py +35 -0
- redeploy-0.2.58/redeploy/models/plan.py +83 -0
- redeploy-0.2.58/redeploy/models/spec.py +182 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy.egg-info/PKG-INFO +6 -6
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy.egg-info/SOURCES.txt +9 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/apply/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/apply/exceptions.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/apply/executor.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/apply/handlers.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/apply/progress.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/apply/rollback.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/apply/state.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/apply/state_apply.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/apply/utils/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/apply/utils/run_container_build.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/audit.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/blueprint/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/blueprint/extractor.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/blueprint/generators/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/blueprint/generators/docker_compose.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/blueprint/generators/migration.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/blueprint/sources/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/blueprint/sources/compose.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/blueprint/sources/hardware.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/blueprint/sources/infra.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/blueprint/sources/migration.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/audit.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/blueprint.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/bump_fix.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/detect.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/device_map.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/device_map_renderers.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/devices.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/diagnose.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/diff.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/exec_.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/export.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/hardware.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/import_.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/init.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/inspect.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/mcp_cmd.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/patterns.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/plan_apply.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/plugin.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/probe.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/prompt_cmd.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/push.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/state.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/status.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/target.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/version/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/version/commands.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/version/helpers.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/version/monorepo.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/version/release.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/version/scanner.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/workflow.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/core.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/display.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/query.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/config_apply/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/config_apply/applier.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/config_apply/loader.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/data_sync.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/detect/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/detect/builtin/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/detect/detector.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/detect/hardware.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/detect/hardware_rules.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/detect/probes.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/detect/remote.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/detect/templates.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/detect/workflow.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/discovery.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/dsl/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/dsl/loader.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/dsl/parser.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/dsl_python/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/dsl_python/context.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/dsl_python/decorators.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/dsl_python/docker_steps.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/dsl_python/exceptions.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/dsl_python/runner.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/dsl_python/steps.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/fleet.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/config_txt.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/data/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/data/hyperpixel.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/data/official.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/data/waveshare.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/fixes.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/kiosk/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/kiosk/autostart.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/kiosk/browsers.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/kiosk/compositors.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/kiosk/output_profiles.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/panels.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/raspi_config.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/heal/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/heal/decider.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/heal/hint_provider.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/heal/log_writer.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/heal/loop_detector.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/heal/runner.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/heal.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/iac/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/iac/base.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/iac/config_hints.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/iac/parsers/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/iac/registry.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/integrations/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/integrations/op3_bridge.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/markpact/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/markpact/compiler.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/markpact/models.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/markpact/parser.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/mcp_server.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/models.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/observe.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/parse.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/patterns.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/plan/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/plan/planner.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/plugins/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/plugins/builtin/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/plugins/builtin/browser_reload.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/plugins/builtin/hardware_diagnostic.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/plugins/builtin/notify.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/plugins/builtin/process_control.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/plugins/builtin/systemd_reload.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/schema.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/spec_loader.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/ssh.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/steps/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/steps/builtins.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/steps/kiosk.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_audit.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_cli.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_data_sync.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_discovery.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_dsl.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_examples.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_executor.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_fleet.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_git_integration.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_heal.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_heal_decider.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_hooks.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_iac.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_iac_compose_contract.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_iac_docker_compose_contract.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_manifest.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_markpact_compiler.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_markpact_parser.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_models.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_observe.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_parse.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_patterns.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_planner_edge.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_plugins.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_probes.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_public_api.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_resume.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_spec_loader.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_ssh.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_templates.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_verify.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_version.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_version_changelog.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_version_cli.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_version_manifest.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_version_sources.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_workflow.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/verify.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/bump.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/changelog.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/commits.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/diff.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/git_integration.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/git_transaction.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/manifest.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/sources/__init__.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/sources/base.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/sources/json_.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/sources/plain.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/sources/regex.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/sources/toml_.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/sources/yaml_.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/transaction.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy.egg-info/dependency_links.txt +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy.egg-info/entry_points.txt +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy.egg-info/requires.txt +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/redeploy.egg-info/top_level.txt +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/setup.cfg +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_blueprint_sources.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_blueprint_sources_compose.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_cli_query.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_example_argocd_flux_plugin.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_example_gitops_ci_plugin.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_example_helm_kustomize_plugin.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_hardware_config.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_hardware_kiosk.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_hardware_rules.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_heal_decider.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_heal_loop_detector.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_iac.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_iac_config_hints.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_iac_plugin_loading.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_import_plugin_template.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_parse.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_prompt_llm.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_redeploy.py +0 -0
- {redeploy-0.2.56 → redeploy-0.2.58}/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.58
|
|
4
4
|
Summary: Infrastructure migration toolkit: detect → plan → apply
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -32,17 +32,17 @@ Requires-Dist: mcp>=1.0; extra == "mcp"
|
|
|
32
32
|
|
|
33
33
|
## AI Cost Tracking
|
|
34
34
|
|
|
35
|
-
     
|
|
36
|
+
  
|
|
37
37
|
|
|
38
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
39
|
-
- 👤 **Human dev:** ~$
|
|
38
|
+
- 🤖 **LLM usage:** $7.5000 (78 commits)
|
|
39
|
+
- 👤 **Human dev:** ~$2890 (28.9h @ $100/h, 30min dedup)
|
|
40
40
|
|
|
41
41
|
Generated on 2026-04-21 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
42
42
|
|
|
43
43
|
---
|
|
44
44
|
|
|
45
|
-
    
|
|
46
46
|
|
|
47
47
|
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
|
|
48
48
|
|
|
@@ -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 (78 commits)
|
|
10
|
+
- 👤 **Human dev:** ~$2890 (28.9h @ $100/h, 30min dedup)
|
|
11
11
|
|
|
12
12
|
Generated on 2026-04-21 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
    
|
|
17
17
|
|
|
18
18
|
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
|
|
19
19
|
|
|
@@ -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.58"
|
|
11
11
|
|
|
12
12
|
# ── Core models ───────────────────────────────────────────────────────────────
|
|
13
13
|
from .models import ( # noqa: F401
|
|
@@ -399,6 +399,40 @@ class DockerComposeParser:
|
|
|
399
399
|
def _parse_service_command(self, cfg: dict) -> Optional[str]:
|
|
400
400
|
return str(cfg["command"]) if cfg.get("command") else None
|
|
401
401
|
|
|
402
|
+
def _build_service_info(
|
|
403
|
+
self,
|
|
404
|
+
*,
|
|
405
|
+
name: str,
|
|
406
|
+
cfg: dict,
|
|
407
|
+
image: Optional[str],
|
|
408
|
+
ports: list[PortInfo],
|
|
409
|
+
volumes: list[VolumeInfo],
|
|
410
|
+
env: dict[str, str],
|
|
411
|
+
env_files: list[str],
|
|
412
|
+
svc_networks: list[str],
|
|
413
|
+
depends_on: list[str],
|
|
414
|
+
healthcheck: Optional[str],
|
|
415
|
+
build_context: Optional[str],
|
|
416
|
+
replicas: int,
|
|
417
|
+
labels: dict[str, str],
|
|
418
|
+
) -> ServiceInfo:
|
|
419
|
+
return ServiceInfo(
|
|
420
|
+
name=name,
|
|
421
|
+
image=image,
|
|
422
|
+
ports=ports,
|
|
423
|
+
volumes=volumes,
|
|
424
|
+
env=env,
|
|
425
|
+
env_files=env_files,
|
|
426
|
+
networks=svc_networks,
|
|
427
|
+
depends_on=depends_on,
|
|
428
|
+
healthcheck=healthcheck,
|
|
429
|
+
restart=cfg.get("restart"),
|
|
430
|
+
command=self._parse_service_command(cfg),
|
|
431
|
+
build_context=build_context,
|
|
432
|
+
replicas=replicas,
|
|
433
|
+
labels=labels,
|
|
434
|
+
)
|
|
435
|
+
|
|
402
436
|
def _parse_service(self, name: str, cfg: dict,
|
|
403
437
|
env_context: dict[str, str],
|
|
404
438
|
spec: ParsedSpec) -> ServiceInfo:
|
|
@@ -413,18 +447,17 @@ class DockerComposeParser:
|
|
|
413
447
|
replicas = self._parse_service_replicas(cfg)
|
|
414
448
|
labels = self._parse_service_labels(cfg)
|
|
415
449
|
|
|
416
|
-
return
|
|
450
|
+
return self._build_service_info(
|
|
417
451
|
name=name,
|
|
452
|
+
cfg=cfg,
|
|
418
453
|
image=image,
|
|
419
454
|
ports=ports,
|
|
420
455
|
volumes=volumes,
|
|
421
456
|
env=env,
|
|
422
457
|
env_files=env_files,
|
|
423
|
-
|
|
458
|
+
svc_networks=svc_networks,
|
|
424
459
|
depends_on=depends_on,
|
|
425
460
|
healthcheck=healthcheck,
|
|
426
|
-
restart=cfg.get("restart"),
|
|
427
|
-
command=self._parse_service_command(cfg),
|
|
428
461
|
build_context=build_context,
|
|
429
462
|
replicas=replicas,
|
|
430
463
|
labels=labels,
|
|
@@ -20,7 +20,6 @@ from typing import Any, Optional
|
|
|
20
20
|
import yaml
|
|
21
21
|
|
|
22
22
|
from ..base import (
|
|
23
|
-
ConversionWarning,
|
|
24
23
|
ParsedSpec,
|
|
25
24
|
PortInfo,
|
|
26
25
|
ServiceInfo,
|
|
@@ -91,8 +90,7 @@ class DockerComposeParser:
|
|
|
91
90
|
spec.networks.append(net_name)
|
|
92
91
|
|
|
93
92
|
# top-level volumes
|
|
94
|
-
for vol_name,
|
|
95
|
-
vol_def = vol_def or {}
|
|
93
|
+
for vol_name, _ in (raw.get("volumes") or {}).items():
|
|
96
94
|
spec.volumes.append(VolumeInfo(
|
|
97
95
|
target="",
|
|
98
96
|
source=vol_name,
|
|
@@ -131,6 +129,12 @@ class DockerComposeParser:
|
|
|
131
129
|
if svc.image and svc.image not in spec.images:
|
|
132
130
|
spec.images.append(svc.image)
|
|
133
131
|
|
|
132
|
+
def _parse_service_networks(self, d: dict) -> list[str]:
|
|
133
|
+
return list(d.get("networks") or [])
|
|
134
|
+
|
|
135
|
+
def _parse_service_replicas(self, d: dict) -> int:
|
|
136
|
+
return int((d.get("deploy") or {}).get("replicas", 1))
|
|
137
|
+
|
|
134
138
|
def _parse_service(self, name: str, d: dict, spec: ParsedSpec) -> ServiceInfo:
|
|
135
139
|
svc = ServiceInfo(name=name)
|
|
136
140
|
|
|
@@ -140,11 +144,11 @@ class DockerComposeParser:
|
|
|
140
144
|
svc.build_context = self._parse_build(d.get("build"))
|
|
141
145
|
svc.command = self._parse_command(d.get("command"))
|
|
142
146
|
svc.restart = d.get("restart") or None
|
|
143
|
-
svc.replicas =
|
|
147
|
+
svc.replicas = self._parse_service_replicas(d)
|
|
144
148
|
|
|
145
149
|
svc.ports = self._parse_ports(d.get("ports") or [], spec)
|
|
146
150
|
svc.volumes = self._parse_volumes(d.get("volumes") or [], spec)
|
|
147
|
-
svc.networks =
|
|
151
|
+
svc.networks = self._parse_service_networks(d)
|
|
148
152
|
svc.env, svc.env_files = self._parse_env(d, spec)
|
|
149
153
|
svc.depends_on = self._parse_depends_on(d.get("depends_on"))
|
|
150
154
|
svc.labels = self._parse_labels(d.get("labels"))
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Device blueprint models — portable deployment recipes."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from .hardware import HardwareInfo
|
|
11
|
+
from .persisted import PersistedModel
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_DEFAULT_BLUEPRINT_DIR = Path.home() / ".config" / "redeploy" / "blueprints"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ServicePort(BaseModel):
|
|
18
|
+
host: int
|
|
19
|
+
container: int
|
|
20
|
+
protocol: str = "tcp"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class VolumeMount(BaseModel):
|
|
24
|
+
host: str
|
|
25
|
+
container: str
|
|
26
|
+
read_only: bool = False
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ServiceSpec(BaseModel):
|
|
30
|
+
name: str
|
|
31
|
+
image: str
|
|
32
|
+
platform: str = ""
|
|
33
|
+
ports: list[ServicePort] = Field(default_factory=list)
|
|
34
|
+
volumes: list[VolumeMount] = Field(default_factory=list)
|
|
35
|
+
env: dict[str, str] = Field(default_factory=dict)
|
|
36
|
+
command: Optional[str] = None
|
|
37
|
+
healthcheck: Optional[str] = None
|
|
38
|
+
depends_on: list[str] = Field(default_factory=list)
|
|
39
|
+
labels: dict[str, str] = Field(default_factory=dict)
|
|
40
|
+
restart: str = "unless-stopped"
|
|
41
|
+
network_mode: Optional[str] = None
|
|
42
|
+
privileged: bool = False
|
|
43
|
+
source_ref: Optional[str] = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class HardwareRequirements(BaseModel):
|
|
47
|
+
arch: str = "linux/arm64"
|
|
48
|
+
min_ram_mb: int = 0
|
|
49
|
+
min_disk_gb: int = 0
|
|
50
|
+
display_type: Optional[str] = None
|
|
51
|
+
display_resolution: Optional[str] = None
|
|
52
|
+
i2c_required: bool = False
|
|
53
|
+
gpio_required: bool = False
|
|
54
|
+
features: list[str] = Field(default_factory=list)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class BlueprintSource(BaseModel):
|
|
58
|
+
device_id: Optional[str] = None
|
|
59
|
+
device_name: Optional[str] = None
|
|
60
|
+
migration_file: Optional[str] = None
|
|
61
|
+
markpact_files: list[str] = Field(default_factory=list)
|
|
62
|
+
compose_files: list[str] = Field(default_factory=list)
|
|
63
|
+
extracted_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class DeviceBlueprint(PersistedModel):
|
|
67
|
+
"""Self-contained, portable deployment recipe."""
|
|
68
|
+
name: str
|
|
69
|
+
version: str = "0.0.0"
|
|
70
|
+
description: str = ""
|
|
71
|
+
tags: list[str] = Field(default_factory=list)
|
|
72
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
73
|
+
|
|
74
|
+
services: list[ServiceSpec] = Field(default_factory=list)
|
|
75
|
+
hardware: HardwareRequirements = Field(default_factory=HardwareRequirements)
|
|
76
|
+
app_url: Optional[str] = None
|
|
77
|
+
deploy_strategy: str = "podman_quadlet"
|
|
78
|
+
env_file: Optional[str] = None
|
|
79
|
+
notes: str = ""
|
|
80
|
+
source: BlueprintSource = Field(default_factory=BlueprintSource)
|
|
81
|
+
|
|
82
|
+
def service(self, name: str) -> Optional[ServiceSpec]:
|
|
83
|
+
return next((s for s in self.services if s.name == name), None)
|
|
84
|
+
|
|
85
|
+
def save(self, path: Optional[Path] = None) -> Path:
|
|
86
|
+
p = path or _DEFAULT_BLUEPRINT_DIR / f"{self.name}.blueprint.yaml"
|
|
87
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
88
|
+
p.write_text(self.to_yaml())
|
|
89
|
+
return p
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def list_saved(cls) -> list[Path]:
|
|
93
|
+
if not _DEFAULT_BLUEPRINT_DIR.exists():
|
|
94
|
+
return []
|
|
95
|
+
return sorted(_DEFAULT_BLUEPRINT_DIR.glob("*.blueprint.yaml"))
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Shared enums and type aliases for redeploy models."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ConflictSeverity(str, Enum):
|
|
8
|
+
LOW = "low"
|
|
9
|
+
MEDIUM = "medium"
|
|
10
|
+
HIGH = "high"
|
|
11
|
+
CRITICAL = "critical"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class StepAction(str, Enum):
|
|
15
|
+
SYSTEMCTL_STOP = "systemctl_stop"
|
|
16
|
+
SYSTEMCTL_DISABLE = "systemctl_disable"
|
|
17
|
+
SYSTEMCTL_START = "systemctl_start"
|
|
18
|
+
KUBECTL_DELETE = "kubectl_delete"
|
|
19
|
+
DOCKER_COMPOSE_UP = "docker_compose_up"
|
|
20
|
+
DOCKER_COMPOSE_DOWN = "docker_compose_down"
|
|
21
|
+
DOCKER_BUILD = "docker_build"
|
|
22
|
+
DOCKER_HEALTH_WAIT = "docker_health_wait"
|
|
23
|
+
CONTAINER_LOG_TAIL = "container_log_tail"
|
|
24
|
+
PODMAN_BUILD = "podman_build"
|
|
25
|
+
RSYNC = "rsync"
|
|
26
|
+
SCP = "scp"
|
|
27
|
+
SSH_CMD = "ssh_cmd"
|
|
28
|
+
HTTP_CHECK = "http_check"
|
|
29
|
+
VERSION_CHECK = "version_check"
|
|
30
|
+
WAIT = "wait"
|
|
31
|
+
PLUGIN = "plugin"
|
|
32
|
+
INLINE_SCRIPT = "inline_script"
|
|
33
|
+
ENSURE_CONFIG_LINE = "ensure_config_line"
|
|
34
|
+
RASPI_CONFIG = "raspi_config"
|
|
35
|
+
ENSURE_KANSHI_PROFILE = "ensure_kanshi_profile"
|
|
36
|
+
ENSURE_AUTOSTART_ENTRY = "ensure_autostart_entry"
|
|
37
|
+
ENSURE_BROWSER_KIOSK_SCRIPT = "ensure_browser_kiosk_script"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class StepStatus(str, Enum):
|
|
41
|
+
PENDING = "pending"
|
|
42
|
+
RUNNING = "running"
|
|
43
|
+
DONE = "done"
|
|
44
|
+
FAILED = "failed"
|
|
45
|
+
SKIPPED = "skipped"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class DeployStrategy(str, Enum):
|
|
49
|
+
DOCKER_FULL = "docker_full"
|
|
50
|
+
NATIVE_KIOSK = "native_kiosk"
|
|
51
|
+
DOCKER_KIOSK = "docker_kiosk"
|
|
52
|
+
KIOSK_APPLIANCE = "kiosk_appliance"
|
|
53
|
+
PODMAN_QUADLET = "podman_quadlet"
|
|
54
|
+
K3S = "k3s"
|
|
55
|
+
SYSTEMD = "systemd"
|
|
56
|
+
UNKNOWN = "unknown"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# doql / external tool aliases → canonical DeployStrategy values
|
|
60
|
+
_STRATEGY_ALIASES: dict[str, str] = {
|
|
61
|
+
"docker-compose": "docker_full",
|
|
62
|
+
"quadlet": "podman_quadlet",
|
|
63
|
+
"kiosk-appliance": "kiosk_appliance",
|
|
64
|
+
"kiosk_appliance": "kiosk_appliance",
|
|
65
|
+
"kubernetes": "k3s",
|
|
66
|
+
"k8s": "k3s",
|
|
67
|
+
"native-kiosk": "native_kiosk",
|
|
68
|
+
"docker-kiosk": "docker_kiosk",
|
|
69
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Hardware models — output of hardware probe."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DrmOutput(BaseModel):
|
|
11
|
+
"""One DRM connector (e.g. card1-DSI-2, card2-HDMI-A-1)."""
|
|
12
|
+
name: str # e.g. "card1-DSI-2"
|
|
13
|
+
connector: str # e.g. "DSI-2", "HDMI-A-1"
|
|
14
|
+
status: str = "unknown" # connected / disconnected
|
|
15
|
+
enabled: str = "unknown" # enabled / disabled
|
|
16
|
+
modes: list[str] = Field(default_factory=list) # e.g. ["1280x800@60"]
|
|
17
|
+
transform: str = "normal"
|
|
18
|
+
position: str = "0,0"
|
|
19
|
+
scale: str = "1.0"
|
|
20
|
+
edid_bytes: int = 0 # EDID size in bytes; 0 = no physical panel
|
|
21
|
+
power_state: Optional[str] = None # DPMS: "on", "off", "suspend"
|
|
22
|
+
sysfs_path: str = "" # e.g. /sys/class/drm/card1-DSI-2
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BacklightInfo(BaseModel):
|
|
26
|
+
"""Sysfs backlight device."""
|
|
27
|
+
name: str # e.g. "11-0045"
|
|
28
|
+
brightness: int = 0
|
|
29
|
+
max_brightness: int = 255
|
|
30
|
+
bl_power: int = 0 # 0=on, 4=off
|
|
31
|
+
display_name: Optional[str] = None # e.g. "DSI-2"
|
|
32
|
+
sysfs_path: str = "" # e.g. /sys/class/backlight/11-0045
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class I2CBusInfo(BaseModel):
|
|
36
|
+
bus: int
|
|
37
|
+
devices: list[str] = Field(default_factory=list) # hex addresses found
|
|
38
|
+
sysfs_path: str = "" # e.g. /dev/i2c-11
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class HardwareDiagnostic(BaseModel):
|
|
42
|
+
"""Problem found during hardware probe."""
|
|
43
|
+
component: str # "dsi", "backlight", "i2c", "gpio", "overlay"
|
|
44
|
+
severity: str = "warning" # info / warning / error / critical
|
|
45
|
+
message: str
|
|
46
|
+
fix: Optional[str] = None # suggested fix command or action
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class HardwareInfo(BaseModel):
|
|
50
|
+
"""Hardware state produced by hardware probe."""
|
|
51
|
+
board: Optional[str] = None # e.g. "Raspberry Pi 5 Model B Rev 1.0"
|
|
52
|
+
kernel: Optional[str] = None # uname -r
|
|
53
|
+
config_txt: str = "" # full /boot/firmware/config.txt
|
|
54
|
+
config_txt_path: str = "/boot/firmware/config.txt"
|
|
55
|
+
|
|
56
|
+
# Display
|
|
57
|
+
drm_outputs: list[DrmOutput] = Field(default_factory=list)
|
|
58
|
+
backlights: list[BacklightInfo] = Field(default_factory=list)
|
|
59
|
+
framebuffers: list[str] = Field(default_factory=list) # /dev/fb* names
|
|
60
|
+
wlr_outputs: list[dict] = Field(default_factory=list) # raw wlr-randr output
|
|
61
|
+
|
|
62
|
+
# DSI specific
|
|
63
|
+
dsi_overlays: list[str] = Field(default_factory=list) # active dtoverlay lines
|
|
64
|
+
dsi_dmesg: list[str] = Field(default_factory=list) # relevant dmesg lines
|
|
65
|
+
dsi_dmesg_errors: list[str] = Field(default_factory=list)
|
|
66
|
+
|
|
67
|
+
# I2C
|
|
68
|
+
i2c_buses: list[I2CBusInfo] = Field(default_factory=list)
|
|
69
|
+
|
|
70
|
+
# Kernel modules
|
|
71
|
+
kernel_modules: list[str] = Field(default_factory=list) # lsmod filtered
|
|
72
|
+
|
|
73
|
+
# Wayland/compositor runtime state
|
|
74
|
+
wayland_sockets: list[str] = Field(default_factory=list) # e.g. ["wayland-0"]
|
|
75
|
+
compositor_processes: dict[str, list[int]] = Field(default_factory=dict)
|
|
76
|
+
|
|
77
|
+
# Diagnostics
|
|
78
|
+
diagnostics: list[HardwareDiagnostic] = Field(default_factory=list)
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def has_dsi(self) -> bool:
|
|
82
|
+
return any("DSI" in o.name for o in self.drm_outputs)
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def kms_enabled(self) -> bool:
|
|
86
|
+
"""True if vc4-kms-v3d overlay is present in config.txt."""
|
|
87
|
+
return any(
|
|
88
|
+
re.match(r'\s*dtoverlay=vc4-kms-v3d', line)
|
|
89
|
+
for line in self.config_txt.splitlines()
|
|
90
|
+
if not line.strip().startswith("#")
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def dsi_connected(self) -> bool:
|
|
95
|
+
"""DRM reports connected — always True on RPi5 when DSI overlay is loaded."""
|
|
96
|
+
return any("DSI" in o.name and o.status == "connected" for o in self.drm_outputs)
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def dsi_physically_connected(self) -> bool:
|
|
100
|
+
"""True only when a physical panel is present (EDID > 0 bytes)."""
|
|
101
|
+
return any("DSI" in o.name and o.edid_bytes > 0 for o in self.drm_outputs)
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def dsi_enabled(self) -> bool:
|
|
105
|
+
return any("DSI" in o.name and o.enabled == "enabled" for o in self.drm_outputs)
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def backlight_on(self) -> bool:
|
|
109
|
+
return any(b.bl_power == 0 and b.brightness > 0 for b in self.backlights)
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def errors(self) -> list[HardwareDiagnostic]:
|
|
113
|
+
return [d for d in self.diagnostics if d.severity in ("error", "critical")]
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def warnings(self) -> list[HardwareDiagnostic]:
|
|
117
|
+
return [d for d in self.diagnostics if d.severity == "warning"]
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Infrastructure state models — output of `detect`."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
from .enums import ConflictSeverity
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ServiceInfo(BaseModel):
|
|
13
|
+
name: str
|
|
14
|
+
image: Optional[str] = None
|
|
15
|
+
status: str = "unknown"
|
|
16
|
+
version: Optional[str] = None
|
|
17
|
+
ports: list[int] = Field(default_factory=list)
|
|
18
|
+
labels: dict[str, str] = Field(default_factory=dict)
|
|
19
|
+
namespace: Optional[str] = None # k3s/k8s namespace
|
|
20
|
+
unit: Optional[str] = None # systemd unit name
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PortInfo(BaseModel):
|
|
24
|
+
port: int
|
|
25
|
+
process: str
|
|
26
|
+
pid: Optional[int] = None
|
|
27
|
+
via: Optional[str] = None # "traefik", "nginx", "ingress-nginx"
|
|
28
|
+
protocol: str = "tcp"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ConflictInfo(BaseModel):
|
|
32
|
+
type: str # "port_steal", "version_mismatch", "duplicate_service"
|
|
33
|
+
description: str
|
|
34
|
+
severity: ConflictSeverity = ConflictSeverity.MEDIUM
|
|
35
|
+
affected: list[str] = Field(default_factory=list)
|
|
36
|
+
fix_hint: Optional[str] = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RuntimeInfo(BaseModel):
|
|
40
|
+
docker: Optional[str] = None
|
|
41
|
+
docker_compose: Optional[str] = None
|
|
42
|
+
k3s: Optional[str] = None
|
|
43
|
+
k3s_namespaces: list[str] = Field(default_factory=list)
|
|
44
|
+
podman: Optional[str] = None
|
|
45
|
+
systemd: Optional[str] = None
|
|
46
|
+
os: Optional[str] = None
|
|
47
|
+
arch: Optional[str] = None
|
|
48
|
+
chromium: Optional[str] = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AppHealthInfo(BaseModel):
|
|
52
|
+
url: str
|
|
53
|
+
status_code: Optional[int] = None
|
|
54
|
+
version: Optional[str] = None
|
|
55
|
+
healthy: bool = False
|
|
56
|
+
response_ms: Optional[float] = None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class InfraState(BaseModel):
|
|
60
|
+
"""Full detected state of infrastructure — output of `detect`."""
|
|
61
|
+
host: str # "root@1.2.3.4" or "local"
|
|
62
|
+
app: str = "unknown"
|
|
63
|
+
scanned_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
64
|
+
|
|
65
|
+
runtime: RuntimeInfo = Field(default_factory=RuntimeInfo)
|
|
66
|
+
ports: dict[int, PortInfo] = Field(default_factory=dict)
|
|
67
|
+
|
|
68
|
+
services: dict[str, list[ServiceInfo]] = Field(
|
|
69
|
+
default_factory=lambda: {"docker": [], "k3s": [], "systemd": [], "podman": []}
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
health: list[AppHealthInfo] = Field(default_factory=list)
|
|
73
|
+
conflicts: list[ConflictInfo] = Field(default_factory=list)
|
|
74
|
+
raw: dict[str, Any] = Field(default_factory=dict) # raw probe output
|
|
75
|
+
|
|
76
|
+
detected_strategy: str = "unknown" # DeployStrategy value
|
|
77
|
+
current_version: Optional[str] = None
|