redeploy 0.2.57__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.57 → redeploy-0.2.58}/PKG-INFO +5 -5
  2. {redeploy-0.2.57 → redeploy-0.2.58}/README.md +4 -4
  3. {redeploy-0.2.57 → redeploy-0.2.58}/pyproject.toml +1 -1
  4. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/__init__.py +1 -1
  5. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/iac/docker_compose.py +37 -4
  6. redeploy-0.2.58/redeploy/models/blueprint.py +95 -0
  7. redeploy-0.2.58/redeploy/models/hardware.py +117 -0
  8. redeploy-0.2.58/redeploy/models/infra.py +77 -0
  9. redeploy-0.2.58/redeploy/models/manifest.py +151 -0
  10. redeploy-0.2.58/redeploy/models/pipeline.py +35 -0
  11. redeploy-0.2.58/redeploy/models/plan.py +83 -0
  12. redeploy-0.2.58/redeploy/models/spec.py +182 -0
  13. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy.egg-info/PKG-INFO +5 -5
  14. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy.egg-info/SOURCES.txt +7 -0
  15. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/apply/__init__.py +0 -0
  16. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/apply/exceptions.py +0 -0
  17. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/apply/executor.py +0 -0
  18. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/apply/handlers.py +0 -0
  19. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/apply/progress.py +0 -0
  20. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/apply/rollback.py +0 -0
  21. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/apply/state.py +0 -0
  22. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/apply/state_apply.py +0 -0
  23. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/apply/utils/__init__.py +0 -0
  24. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/apply/utils/run_container_build.py +0 -0
  25. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/audit.py +0 -0
  26. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/blueprint/__init__.py +0 -0
  27. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/blueprint/extractor.py +0 -0
  28. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/blueprint/generators/__init__.py +0 -0
  29. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/blueprint/generators/docker_compose.py +0 -0
  30. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/blueprint/generators/migration.py +0 -0
  31. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/blueprint/sources/__init__.py +0 -0
  32. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/blueprint/sources/compose.py +0 -0
  33. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/blueprint/sources/hardware.py +0 -0
  34. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/blueprint/sources/infra.py +0 -0
  35. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/blueprint/sources/migration.py +0 -0
  36. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/__init__.py +0 -0
  37. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/__init__.py +0 -0
  38. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/audit.py +0 -0
  39. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/blueprint.py +0 -0
  40. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/bump_fix.py +0 -0
  41. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/detect.py +0 -0
  42. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/device_map.py +0 -0
  43. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/device_map_renderers.py +0 -0
  44. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/devices.py +0 -0
  45. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/diagnose.py +0 -0
  46. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/diff.py +0 -0
  47. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/exec_.py +0 -0
  48. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/export.py +0 -0
  49. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/hardware.py +0 -0
  50. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/import_.py +0 -0
  51. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/init.py +0 -0
  52. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/inspect.py +0 -0
  53. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/mcp_cmd.py +0 -0
  54. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/patterns.py +0 -0
  55. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/plan_apply.py +0 -0
  56. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/plugin.py +0 -0
  57. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/probe.py +0 -0
  58. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/prompt_cmd.py +0 -0
  59. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/push.py +0 -0
  60. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/state.py +0 -0
  61. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/status.py +0 -0
  62. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/target.py +0 -0
  63. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/version/__init__.py +0 -0
  64. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/version/commands.py +0 -0
  65. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/version/helpers.py +0 -0
  66. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/version/monorepo.py +0 -0
  67. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/version/release.py +0 -0
  68. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/version/scanner.py +0 -0
  69. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/commands/workflow.py +0 -0
  70. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/core.py +0 -0
  71. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/display.py +0 -0
  72. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli/query.py +0 -0
  73. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/cli.py +0 -0
  74. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/config_apply/__init__.py +0 -0
  75. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/config_apply/applier.py +0 -0
  76. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/config_apply/loader.py +0 -0
  77. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/data_sync.py +0 -0
  78. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/detect/__init__.py +0 -0
  79. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/detect/builtin/__init__.py +0 -0
  80. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/detect/detector.py +0 -0
  81. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/detect/hardware.py +0 -0
  82. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/detect/hardware_rules.py +0 -0
  83. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/detect/probes.py +0 -0
  84. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/detect/remote.py +0 -0
  85. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/detect/templates.py +0 -0
  86. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/detect/workflow.py +0 -0
  87. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/discovery.py +0 -0
  88. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/dsl/__init__.py +0 -0
  89. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/dsl/loader.py +0 -0
  90. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/dsl/parser.py +0 -0
  91. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/dsl_python/__init__.py +0 -0
  92. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/dsl_python/context.py +0 -0
  93. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/dsl_python/decorators.py +0 -0
  94. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/dsl_python/docker_steps.py +0 -0
  95. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/dsl_python/exceptions.py +0 -0
  96. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/dsl_python/runner.py +0 -0
  97. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/dsl_python/steps.py +0 -0
  98. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/fleet.py +0 -0
  99. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/hardware/__init__.py +0 -0
  100. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/hardware/config_txt.py +0 -0
  101. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/hardware/data/__init__.py +0 -0
  102. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/hardware/data/hyperpixel.py +0 -0
  103. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/hardware/data/official.py +0 -0
  104. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/hardware/data/waveshare.py +0 -0
  105. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/hardware/fixes.py +0 -0
  106. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/hardware/kiosk/__init__.py +0 -0
  107. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/hardware/kiosk/autostart.py +0 -0
  108. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/hardware/kiosk/browsers.py +0 -0
  109. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/hardware/kiosk/compositors.py +0 -0
  110. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/hardware/kiosk/output_profiles.py +0 -0
  111. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/hardware/panels.py +0 -0
  112. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/hardware/raspi_config.py +0 -0
  113. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/heal/__init__.py +0 -0
  114. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/heal/decider.py +0 -0
  115. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/heal/hint_provider.py +0 -0
  116. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/heal/log_writer.py +0 -0
  117. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/heal/loop_detector.py +0 -0
  118. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/heal/runner.py +0 -0
  119. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/heal.py +0 -0
  120. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/iac/__init__.py +0 -0
  121. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/iac/base.py +0 -0
  122. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/iac/config_hints.py +0 -0
  123. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/iac/parsers/__init__.py +0 -0
  124. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/iac/parsers/compose.py +0 -0
  125. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/iac/registry.py +0 -0
  126. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/integrations/__init__.py +0 -0
  127. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/integrations/op3_bridge.py +0 -0
  128. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/markpact/__init__.py +0 -0
  129. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/markpact/compiler.py +0 -0
  130. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/markpact/models.py +0 -0
  131. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/markpact/parser.py +0 -0
  132. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/mcp_server.py +0 -0
  133. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/models/enums.py +0 -0
  134. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/models/persisted.py +0 -0
  135. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/models.py +0 -0
  136. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/observe.py +0 -0
  137. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/parse.py +0 -0
  138. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/patterns.py +0 -0
  139. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/plan/__init__.py +0 -0
  140. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/plan/planner.py +0 -0
  141. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/plugins/__init__.py +0 -0
  142. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/plugins/builtin/__init__.py +0 -0
  143. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/plugins/builtin/browser_reload.py +0 -0
  144. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/plugins/builtin/hardware_diagnostic.py +0 -0
  145. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/plugins/builtin/notify.py +0 -0
  146. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/plugins/builtin/process_control.py +0 -0
  147. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/plugins/builtin/systemd_reload.py +0 -0
  148. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/schema.py +0 -0
  149. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/spec_loader.py +0 -0
  150. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/ssh.py +0 -0
  151. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/steps/__init__.py +0 -0
  152. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/steps/builtins.py +0 -0
  153. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/steps/kiosk.py +0 -0
  154. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/__init__.py +0 -0
  155. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_audit.py +0 -0
  156. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_cli.py +0 -0
  157. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_data_sync.py +0 -0
  158. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_discovery.py +0 -0
  159. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_dsl.py +0 -0
  160. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_examples.py +0 -0
  161. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_executor.py +0 -0
  162. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_fleet.py +0 -0
  163. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_git_integration.py +0 -0
  164. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_heal.py +0 -0
  165. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_heal_decider.py +0 -0
  166. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_hooks.py +0 -0
  167. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_iac.py +0 -0
  168. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_iac_compose_contract.py +0 -0
  169. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_iac_docker_compose_contract.py +0 -0
  170. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_manifest.py +0 -0
  171. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_markpact_compiler.py +0 -0
  172. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_markpact_parser.py +0 -0
  173. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_models.py +0 -0
  174. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_observe.py +0 -0
  175. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_parse.py +0 -0
  176. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_patterns.py +0 -0
  177. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_planner_edge.py +0 -0
  178. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_plugins.py +0 -0
  179. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_probes.py +0 -0
  180. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_public_api.py +0 -0
  181. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_resume.py +0 -0
  182. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_spec_loader.py +0 -0
  183. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_ssh.py +0 -0
  184. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_templates.py +0 -0
  185. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_verify.py +0 -0
  186. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_version.py +0 -0
  187. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_version_changelog.py +0 -0
  188. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_version_cli.py +0 -0
  189. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_version_manifest.py +0 -0
  190. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_version_sources.py +0 -0
  191. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/tests/test_workflow.py +0 -0
  192. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/verify.py +0 -0
  193. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/version/__init__.py +0 -0
  194. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/version/bump.py +0 -0
  195. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/version/changelog.py +0 -0
  196. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/version/commits.py +0 -0
  197. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/version/diff.py +0 -0
  198. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/version/git_integration.py +0 -0
  199. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/version/git_transaction.py +0 -0
  200. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/version/manifest.py +0 -0
  201. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/version/sources/__init__.py +0 -0
  202. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/version/sources/base.py +0 -0
  203. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/version/sources/json_.py +0 -0
  204. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/version/sources/plain.py +0 -0
  205. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/version/sources/regex.py +0 -0
  206. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/version/sources/toml_.py +0 -0
  207. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/version/sources/yaml_.py +0 -0
  208. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/version/transaction.py +0 -0
  209. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy/version.py +0 -0
  210. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy.egg-info/dependency_links.txt +0 -0
  211. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy.egg-info/entry_points.txt +0 -0
  212. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy.egg-info/requires.txt +0 -0
  213. {redeploy-0.2.57 → redeploy-0.2.58}/redeploy.egg-info/top_level.txt +0 -0
  214. {redeploy-0.2.57 → redeploy-0.2.58}/setup.cfg +0 -0
  215. {redeploy-0.2.57 → redeploy-0.2.58}/tests/test_blueprint_sources.py +0 -0
  216. {redeploy-0.2.57 → redeploy-0.2.58}/tests/test_blueprint_sources_compose.py +0 -0
  217. {redeploy-0.2.57 → redeploy-0.2.58}/tests/test_cli_query.py +0 -0
  218. {redeploy-0.2.57 → redeploy-0.2.58}/tests/test_example_argocd_flux_plugin.py +0 -0
  219. {redeploy-0.2.57 → redeploy-0.2.58}/tests/test_example_gitops_ci_plugin.py +0 -0
  220. {redeploy-0.2.57 → redeploy-0.2.58}/tests/test_example_helm_kustomize_plugin.py +0 -0
  221. {redeploy-0.2.57 → redeploy-0.2.58}/tests/test_hardware_config.py +0 -0
  222. {redeploy-0.2.57 → redeploy-0.2.58}/tests/test_hardware_kiosk.py +0 -0
  223. {redeploy-0.2.57 → redeploy-0.2.58}/tests/test_hardware_rules.py +0 -0
  224. {redeploy-0.2.57 → redeploy-0.2.58}/tests/test_heal_decider.py +0 -0
  225. {redeploy-0.2.57 → redeploy-0.2.58}/tests/test_heal_loop_detector.py +0 -0
  226. {redeploy-0.2.57 → redeploy-0.2.58}/tests/test_iac.py +0 -0
  227. {redeploy-0.2.57 → redeploy-0.2.58}/tests/test_iac_config_hints.py +0 -0
  228. {redeploy-0.2.57 → redeploy-0.2.58}/tests/test_iac_plugin_loading.py +0 -0
  229. {redeploy-0.2.57 → redeploy-0.2.58}/tests/test_import_plugin_template.py +0 -0
  230. {redeploy-0.2.57 → redeploy-0.2.58}/tests/test_parse.py +0 -0
  231. {redeploy-0.2.57 → redeploy-0.2.58}/tests/test_prompt_llm.py +0 -0
  232. {redeploy-0.2.57 → redeploy-0.2.58}/tests/test_redeploy.py +0 -0
  233. {redeploy-0.2.57 → 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.57
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.57-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
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
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 (77 commits)
39
- - 👤 **Human dev:** ~$2886 (28.9h @ $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.57-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.57-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
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
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 (77 commits)
10
- - 👤 **Human dev:** ~$2886 (28.9h @ $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.57-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.57"
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.57"
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,
@@ -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,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
@@ -0,0 +1,151 @@
1
+ """Project manifest models — redeploy.yaml + environments."""
2
+ from __future__ import annotations
3
+
4
+ from pathlib import Path
5
+ from typing import Any, Optional
6
+
7
+ import yaml
8
+ from pydantic import BaseModel, Field
9
+
10
+ from .enums import DeployStrategy
11
+
12
+
13
+ class EnvironmentConfig(BaseModel):
14
+ """One named environment in redeploy.yaml."""
15
+ host: Optional[str] = None
16
+ strategy: Optional[str] = None
17
+ app: Optional[str] = None
18
+ domain: Optional[str] = None
19
+ remote_dir: Optional[str] = None
20
+ env_file: Optional[str] = None
21
+ ssh_key: Optional[str] = None
22
+ ssh_port: int = 22
23
+ verify_url: Optional[str] = None
24
+ spec: Optional[str] = None
25
+
26
+
27
+ class ProjectManifest(BaseModel):
28
+ """Per-project redeploy.yaml — replaces repetitive Makefile variables."""
29
+ spec: str = "migration.yaml"
30
+ local_spec: str = "migration-local.yaml"
31
+ host: Optional[str] = None
32
+ app: str = "app"
33
+ domain: Optional[str] = None
34
+ ssh_key: Optional[str] = None
35
+ ssh_port: int = 22
36
+ remote_dir: Optional[str] = None
37
+ env_file: str = ".env"
38
+ environments: dict[str, EnvironmentConfig] = Field(default_factory=dict)
39
+
40
+ @classmethod
41
+ def find_and_load(cls, start: Path) -> Optional[ProjectManifest]:
42
+ """Walk up from *start* looking for redeploy.css/less/yaml."""
43
+ for d in [Path(start)] + list(Path(start).parents):
44
+ for css_name in ("redeploy.css", "redeploy.less"):
45
+ candidate = d / css_name
46
+ if candidate.exists():
47
+ from ..dsl.loader import load_css
48
+ result = load_css(candidate)
49
+ if result.manifest:
50
+ return result.manifest
51
+ candidate = d / "redeploy.yaml"
52
+ if candidate.exists():
53
+ with candidate.open() as f:
54
+ return cls(**yaml.safe_load(f))
55
+ return None
56
+
57
+ @classmethod
58
+ def find_css(cls, start: Path) -> Optional[Path]:
59
+ """Return path to redeploy.css/less if found, else None."""
60
+ for d in [Path(start)] + list(Path(start).parents):
61
+ for name in ("redeploy.css", "redeploy.less"):
62
+ p = d / name
63
+ if p.exists():
64
+ return p
65
+ return None
66
+
67
+ def env(self, name: str) -> Optional[EnvironmentConfig]:
68
+ return self.environments.get(name)
69
+
70
+ def resolve_env(self, name: str) -> EnvironmentConfig:
71
+ base = EnvironmentConfig(
72
+ host=self.host,
73
+ app=self.app,
74
+ domain=self.domain,
75
+ remote_dir=self.remote_dir,
76
+ env_file=self.env_file,
77
+ ssh_key=self.ssh_key,
78
+ ssh_port=self.ssh_port,
79
+ )
80
+ override = self.environments.get(name)
81
+ if not override:
82
+ return base
83
+ return EnvironmentConfig(
84
+ host=override.host or base.host,
85
+ strategy=override.strategy,
86
+ app=override.app or base.app,
87
+ domain=override.domain or base.domain,
88
+ remote_dir=override.remote_dir or base.remote_dir,
89
+ env_file=override.env_file or base.env_file,
90
+ ssh_key=override.ssh_key or base.ssh_key,
91
+ ssh_port=override.ssh_port if override.ssh_port != 22 else base.ssh_port,
92
+ verify_url=override.verify_url,
93
+ spec=override.spec,
94
+ )
95
+
96
+ @classmethod
97
+ def from_dotenv(cls, project_dir: Path) -> Optional[ProjectManifest]:
98
+ """Read DEPLOY_* vars from .env file in project_dir as a fallback."""
99
+ env_path = Path(project_dir) / ".env"
100
+ if not env_path.exists():
101
+ return None
102
+ data: dict = {}
103
+ for line in env_path.read_text().splitlines():
104
+ line = line.strip()
105
+ if not line or line.startswith("#") or "=" not in line:
106
+ continue
107
+ k, _, v = line.partition("=")
108
+ k = k.strip().upper()
109
+ v = v.strip().strip('"').strip("'")
110
+ if k == "DEPLOY_HOST":
111
+ data["host"] = v
112
+ elif k == "DEPLOY_APP":
113
+ data["app"] = v
114
+ elif k == "DEPLOY_DOMAIN":
115
+ data["domain"] = v
116
+ elif k == "DEPLOY_ENV_FILE":
117
+ data["env_file"] = v
118
+ elif k == "DEPLOY_SSH_KEY":
119
+ data["ssh_key"] = v
120
+ if not data:
121
+ return None
122
+ return cls(**data)
123
+
124
+ def apply_to_spec(self, spec: Any, env_name: str = "") -> None:
125
+ """Overlay manifest values onto a MigrationSpec."""
126
+ from .spec import MigrationSpec
127
+ assert isinstance(spec, MigrationSpec)
128
+ cfg = self.resolve_env(env_name) if env_name else self
129
+ host = cfg.host if isinstance(cfg, EnvironmentConfig) else self.host
130
+ domain = cfg.domain if isinstance(cfg, EnvironmentConfig) else self.domain
131
+ remote_dir = cfg.remote_dir if isinstance(cfg, EnvironmentConfig) else self.remote_dir
132
+ env_file = cfg.env_file if isinstance(cfg, EnvironmentConfig) else self.env_file
133
+ verify_url = cfg.verify_url if isinstance(cfg, EnvironmentConfig) else None
134
+ strategy_str = cfg.strategy if isinstance(cfg, EnvironmentConfig) else None
135
+
136
+ if host:
137
+ spec.source.host = host
138
+ spec.target.host = host
139
+ if domain and not spec.target.domain:
140
+ spec.target.domain = domain
141
+ if remote_dir and not spec.target.remote_dir:
142
+ spec.target.remote_dir = remote_dir
143
+ if env_file and not spec.target.env_file:
144
+ spec.target.env_file = env_file
145
+ if verify_url and not spec.target.verify_url:
146
+ spec.target.verify_url = verify_url
147
+ if strategy_str:
148
+ try:
149
+ spec.target.strategy = DeployStrategy(strategy_str)
150
+ except ValueError:
151
+ pass
@@ -0,0 +1,35 @@
1
+ """Pipeline hook models — generic pre/post/failure lifecycle."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Literal, Optional
5
+
6
+ from pydantic import BaseModel
7
+
8
+
9
+ PipelinePhase = Literal[
10
+ "before_plan",
11
+ "before_apply",
12
+ "before_step",
13
+ "after_step",
14
+ "on_step_failure",
15
+ "on_step_retry",
16
+ "after_apply",
17
+ "on_failure",
18
+ "always",
19
+ ]
20
+
21
+
22
+ class Hook(BaseModel):
23
+ """Generyczny hook w pipeline: faza + akcja + opcjonalny warunek.
24
+
25
+ Zastępuje ad-hoc pola typu ``post_deploy``/``pre_deploy``/``insert_before``.
26
+ Dodatkowe pola specyficzne dla akcji (command, url, ...) idą do ``extra``.
27
+ """
28
+ id: str
29
+ phase: PipelinePhase
30
+ action: str # StepAction value (ssh_cmd, http_check, local_cmd, ...)
31
+ description: str = ""
32
+ when: Optional[str] = None # opcjonalny warunek
33
+ on_failure: Literal["abort", "continue", "warn"] = "warn"
34
+
35
+ model_config = {"extra": "allow"} # zachowuje dodatkowe pola (command, url, ...)