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.
Files changed (233) hide show
  1. {redeploy-0.2.56 → redeploy-0.2.58}/PKG-INFO +6 -6
  2. {redeploy-0.2.56 → redeploy-0.2.58}/README.md +5 -5
  3. {redeploy-0.2.56 → redeploy-0.2.58}/pyproject.toml +1 -1
  4. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/__init__.py +1 -1
  5. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/iac/docker_compose.py +37 -4
  6. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/iac/parsers/compose.py +9 -5
  7. redeploy-0.2.58/redeploy/models/blueprint.py +95 -0
  8. redeploy-0.2.58/redeploy/models/enums.py +69 -0
  9. redeploy-0.2.58/redeploy/models/hardware.py +117 -0
  10. redeploy-0.2.58/redeploy/models/infra.py +77 -0
  11. redeploy-0.2.58/redeploy/models/manifest.py +151 -0
  12. redeploy-0.2.58/redeploy/models/persisted.py +27 -0
  13. redeploy-0.2.58/redeploy/models/pipeline.py +35 -0
  14. redeploy-0.2.58/redeploy/models/plan.py +83 -0
  15. redeploy-0.2.58/redeploy/models/spec.py +182 -0
  16. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy.egg-info/PKG-INFO +6 -6
  17. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy.egg-info/SOURCES.txt +9 -0
  18. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/apply/__init__.py +0 -0
  19. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/apply/exceptions.py +0 -0
  20. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/apply/executor.py +0 -0
  21. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/apply/handlers.py +0 -0
  22. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/apply/progress.py +0 -0
  23. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/apply/rollback.py +0 -0
  24. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/apply/state.py +0 -0
  25. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/apply/state_apply.py +0 -0
  26. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/apply/utils/__init__.py +0 -0
  27. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/apply/utils/run_container_build.py +0 -0
  28. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/audit.py +0 -0
  29. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/blueprint/__init__.py +0 -0
  30. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/blueprint/extractor.py +0 -0
  31. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/blueprint/generators/__init__.py +0 -0
  32. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/blueprint/generators/docker_compose.py +0 -0
  33. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/blueprint/generators/migration.py +0 -0
  34. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/blueprint/sources/__init__.py +0 -0
  35. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/blueprint/sources/compose.py +0 -0
  36. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/blueprint/sources/hardware.py +0 -0
  37. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/blueprint/sources/infra.py +0 -0
  38. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/blueprint/sources/migration.py +0 -0
  39. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/__init__.py +0 -0
  40. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/__init__.py +0 -0
  41. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/audit.py +0 -0
  42. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/blueprint.py +0 -0
  43. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/bump_fix.py +0 -0
  44. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/detect.py +0 -0
  45. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/device_map.py +0 -0
  46. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/device_map_renderers.py +0 -0
  47. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/devices.py +0 -0
  48. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/diagnose.py +0 -0
  49. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/diff.py +0 -0
  50. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/exec_.py +0 -0
  51. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/export.py +0 -0
  52. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/hardware.py +0 -0
  53. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/import_.py +0 -0
  54. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/init.py +0 -0
  55. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/inspect.py +0 -0
  56. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/mcp_cmd.py +0 -0
  57. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/patterns.py +0 -0
  58. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/plan_apply.py +0 -0
  59. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/plugin.py +0 -0
  60. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/probe.py +0 -0
  61. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/prompt_cmd.py +0 -0
  62. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/push.py +0 -0
  63. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/state.py +0 -0
  64. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/status.py +0 -0
  65. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/target.py +0 -0
  66. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/version/__init__.py +0 -0
  67. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/version/commands.py +0 -0
  68. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/version/helpers.py +0 -0
  69. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/version/monorepo.py +0 -0
  70. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/version/release.py +0 -0
  71. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/version/scanner.py +0 -0
  72. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/commands/workflow.py +0 -0
  73. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/core.py +0 -0
  74. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/display.py +0 -0
  75. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli/query.py +0 -0
  76. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/cli.py +0 -0
  77. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/config_apply/__init__.py +0 -0
  78. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/config_apply/applier.py +0 -0
  79. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/config_apply/loader.py +0 -0
  80. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/data_sync.py +0 -0
  81. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/detect/__init__.py +0 -0
  82. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/detect/builtin/__init__.py +0 -0
  83. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/detect/detector.py +0 -0
  84. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/detect/hardware.py +0 -0
  85. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/detect/hardware_rules.py +0 -0
  86. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/detect/probes.py +0 -0
  87. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/detect/remote.py +0 -0
  88. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/detect/templates.py +0 -0
  89. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/detect/workflow.py +0 -0
  90. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/discovery.py +0 -0
  91. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/dsl/__init__.py +0 -0
  92. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/dsl/loader.py +0 -0
  93. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/dsl/parser.py +0 -0
  94. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/dsl_python/__init__.py +0 -0
  95. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/dsl_python/context.py +0 -0
  96. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/dsl_python/decorators.py +0 -0
  97. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/dsl_python/docker_steps.py +0 -0
  98. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/dsl_python/exceptions.py +0 -0
  99. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/dsl_python/runner.py +0 -0
  100. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/dsl_python/steps.py +0 -0
  101. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/fleet.py +0 -0
  102. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/__init__.py +0 -0
  103. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/config_txt.py +0 -0
  104. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/data/__init__.py +0 -0
  105. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/data/hyperpixel.py +0 -0
  106. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/data/official.py +0 -0
  107. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/data/waveshare.py +0 -0
  108. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/fixes.py +0 -0
  109. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/kiosk/__init__.py +0 -0
  110. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/kiosk/autostart.py +0 -0
  111. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/kiosk/browsers.py +0 -0
  112. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/kiosk/compositors.py +0 -0
  113. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/kiosk/output_profiles.py +0 -0
  114. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/panels.py +0 -0
  115. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/hardware/raspi_config.py +0 -0
  116. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/heal/__init__.py +0 -0
  117. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/heal/decider.py +0 -0
  118. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/heal/hint_provider.py +0 -0
  119. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/heal/log_writer.py +0 -0
  120. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/heal/loop_detector.py +0 -0
  121. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/heal/runner.py +0 -0
  122. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/heal.py +0 -0
  123. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/iac/__init__.py +0 -0
  124. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/iac/base.py +0 -0
  125. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/iac/config_hints.py +0 -0
  126. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/iac/parsers/__init__.py +0 -0
  127. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/iac/registry.py +0 -0
  128. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/integrations/__init__.py +0 -0
  129. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/integrations/op3_bridge.py +0 -0
  130. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/markpact/__init__.py +0 -0
  131. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/markpact/compiler.py +0 -0
  132. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/markpact/models.py +0 -0
  133. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/markpact/parser.py +0 -0
  134. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/mcp_server.py +0 -0
  135. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/models.py +0 -0
  136. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/observe.py +0 -0
  137. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/parse.py +0 -0
  138. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/patterns.py +0 -0
  139. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/plan/__init__.py +0 -0
  140. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/plan/planner.py +0 -0
  141. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/plugins/__init__.py +0 -0
  142. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/plugins/builtin/__init__.py +0 -0
  143. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/plugins/builtin/browser_reload.py +0 -0
  144. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/plugins/builtin/hardware_diagnostic.py +0 -0
  145. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/plugins/builtin/notify.py +0 -0
  146. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/plugins/builtin/process_control.py +0 -0
  147. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/plugins/builtin/systemd_reload.py +0 -0
  148. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/schema.py +0 -0
  149. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/spec_loader.py +0 -0
  150. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/ssh.py +0 -0
  151. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/steps/__init__.py +0 -0
  152. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/steps/builtins.py +0 -0
  153. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/steps/kiosk.py +0 -0
  154. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/__init__.py +0 -0
  155. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_audit.py +0 -0
  156. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_cli.py +0 -0
  157. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_data_sync.py +0 -0
  158. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_discovery.py +0 -0
  159. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_dsl.py +0 -0
  160. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_examples.py +0 -0
  161. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_executor.py +0 -0
  162. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_fleet.py +0 -0
  163. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_git_integration.py +0 -0
  164. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_heal.py +0 -0
  165. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_heal_decider.py +0 -0
  166. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_hooks.py +0 -0
  167. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_iac.py +0 -0
  168. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_iac_compose_contract.py +0 -0
  169. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_iac_docker_compose_contract.py +0 -0
  170. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_manifest.py +0 -0
  171. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_markpact_compiler.py +0 -0
  172. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_markpact_parser.py +0 -0
  173. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_models.py +0 -0
  174. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_observe.py +0 -0
  175. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_parse.py +0 -0
  176. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_patterns.py +0 -0
  177. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_planner_edge.py +0 -0
  178. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_plugins.py +0 -0
  179. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_probes.py +0 -0
  180. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_public_api.py +0 -0
  181. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_resume.py +0 -0
  182. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_spec_loader.py +0 -0
  183. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_ssh.py +0 -0
  184. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_templates.py +0 -0
  185. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_verify.py +0 -0
  186. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_version.py +0 -0
  187. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_version_changelog.py +0 -0
  188. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_version_cli.py +0 -0
  189. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_version_manifest.py +0 -0
  190. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_version_sources.py +0 -0
  191. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/tests/test_workflow.py +0 -0
  192. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/verify.py +0 -0
  193. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/__init__.py +0 -0
  194. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/bump.py +0 -0
  195. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/changelog.py +0 -0
  196. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/commits.py +0 -0
  197. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/diff.py +0 -0
  198. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/git_integration.py +0 -0
  199. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/git_transaction.py +0 -0
  200. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/manifest.py +0 -0
  201. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/sources/__init__.py +0 -0
  202. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/sources/base.py +0 -0
  203. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/sources/json_.py +0 -0
  204. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/sources/plain.py +0 -0
  205. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/sources/regex.py +0 -0
  206. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/sources/toml_.py +0 -0
  207. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/sources/yaml_.py +0 -0
  208. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version/transaction.py +0 -0
  209. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy/version.py +0 -0
  210. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy.egg-info/dependency_links.txt +0 -0
  211. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy.egg-info/entry_points.txt +0 -0
  212. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy.egg-info/requires.txt +0 -0
  213. {redeploy-0.2.56 → redeploy-0.2.58}/redeploy.egg-info/top_level.txt +0 -0
  214. {redeploy-0.2.56 → redeploy-0.2.58}/setup.cfg +0 -0
  215. {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_blueprint_sources.py +0 -0
  216. {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_blueprint_sources_compose.py +0 -0
  217. {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_cli_query.py +0 -0
  218. {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_example_argocd_flux_plugin.py +0 -0
  219. {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_example_gitops_ci_plugin.py +0 -0
  220. {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_example_helm_kustomize_plugin.py +0 -0
  221. {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_hardware_config.py +0 -0
  222. {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_hardware_kiosk.py +0 -0
  223. {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_hardware_rules.py +0 -0
  224. {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_heal_decider.py +0 -0
  225. {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_heal_loop_detector.py +0 -0
  226. {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_iac.py +0 -0
  227. {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_iac_config_hints.py +0 -0
  228. {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_iac_plugin_loading.py +0 -0
  229. {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_import_plugin_template.py +0 -0
  230. {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_parse.py +0 -0
  231. {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_prompt_llm.py +0 -0
  232. {redeploy-0.2.56 → redeploy-0.2.58}/tests/test_redeploy.py +0 -0
  233. {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.56
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
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.56-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
36
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-28.6h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
35
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.58-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
36
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-28.9h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
37
37
 
38
- - 🤖 **LLM usage:** $7.5000 (76 commits)
39
- - 👤 **Human dev:** ~$2862 (28.6h @ $100/h, 30min dedup)
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
- ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.56-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
45
+ ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.58-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
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
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.56-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-28.6h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
6
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.58-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-28.9h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
8
8
 
9
- - 🤖 **LLM usage:** $7.5000 (76 commits)
10
- - 👤 **Human dev:** ~$2862 (28.6h @ $100/h, 30min dedup)
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
- ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.56-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
16
+ ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.58-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
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.56"
7
+ version = "0.2.58"
8
8
  description = "Infrastructure migration toolkit: detect → plan → apply"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -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.56"
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 ServiceInfo(
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
- networks=svc_networks,
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, vol_def in (raw.get("volumes") or {}).items():
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 = int((d.get("deploy") or {}).get("replicas", 1))
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 = list(d.get("networks") or [])
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