smbcloud-cli 0.6.2__tar.gz → 0.7.0__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 (185) hide show
  1. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/Cargo.lock +17 -17
  2. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/Cargo.toml +12 -12
  3. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/PKG-INFO +1 -1
  4. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/Cargo.toml +1 -1
  5. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-deploy/process_deploy_rust.rs +378 -27
  6. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-deploy/process_migrate.rs +1 -0
  7. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-auth/Cargo.toml +1 -1
  8. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-deploy/Cargo.toml +1 -1
  9. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-email-sdk/Cargo.toml +1 -1
  10. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-mail/Cargo.toml +1 -1
  11. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-model/Cargo.toml +1 -1
  12. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-model/src/deploy_config.rs +4 -2
  13. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-model/src/project.rs +10 -3
  14. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-network/Cargo.toml +1 -1
  15. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking/Cargo.toml +1 -1
  16. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking-project/Cargo.toml +1 -1
  17. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-utils/Cargo.toml +1 -1
  18. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/xcrs/Cargo.toml +1 -1
  19. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/README.md +0 -0
  20. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/README.md +0 -0
  21. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/account/cli.rs +0 -0
  22. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/account/forgot/mod.rs +0 -0
  23. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/account/lib.rs +0 -0
  24. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/account/login/mod.rs +0 -0
  25. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/account/login/process.rs +0 -0
  26. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/account/logout/mod.rs +0 -0
  27. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/account/me/mod.rs +0 -0
  28. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/account/mod.rs +0 -0
  29. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/account/signup/mod.rs +0 -0
  30. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/account/signup/process.rs +0 -0
  31. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/ci.rs +0 -0
  32. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cli/mod.rs +0 -0
  33. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-auth/auth_app.rs +0 -0
  34. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-auth/cli.rs +0 -0
  35. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-auth/mod.rs +0 -0
  36. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-auth/process.rs +0 -0
  37. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-auth/render.rs +0 -0
  38. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-auth/request.rs +0 -0
  39. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-auth/url_builder.rs +0 -0
  40. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-deploy/config.rs +0 -0
  41. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-deploy/git.rs +0 -0
  42. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-deploy/mod.rs +0 -0
  43. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-deploy/process_deploy.rs +0 -0
  44. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-deploy/process_deploy_nextjs_ssr.rs +0 -0
  45. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-deploy/process_deploy_rails.rs +0 -0
  46. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-deploy/process_deploy_static_site.rs +0 -0
  47. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-deploy/process_deploy_swift.rs +0 -0
  48. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-deploy/remote_messages.rs +0 -0
  49. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-deploy/setup.rs +0 -0
  50. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-deploy/setup_create_new_project.rs +0 -0
  51. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-deploy/setup_project.rs +0 -0
  52. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-deploy/setup_select_project.rs +0 -0
  53. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-mail/cli.rs +0 -0
  54. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-mail/current_project.rs +0 -0
  55. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-mail/mod.rs +0 -0
  56. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-mail/process.rs +0 -0
  57. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-mail/render.rs +0 -0
  58. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/cloud-mail/send.rs +0 -0
  59. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/controlkit.rs +0 -0
  60. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/interface.rs +0 -0
  61. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/lib.rs +0 -0
  62. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/main.rs +0 -0
  63. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/mcp.rs +0 -0
  64. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/mcp_xcrs.rs +0 -0
  65. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/project/cli.rs +0 -0
  66. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/project/crud_create.rs +0 -0
  67. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/project/crud_delete.rs +0 -0
  68. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/project/crud_read.rs +0 -0
  69. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/project/crud_update.rs +0 -0
  70. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/project/deploy_target.rs +0 -0
  71. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/project/deployment.rs +0 -0
  72. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/project/mod.rs +0 -0
  73. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/project/process.rs +0 -0
  74. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/session_config.rs +0 -0
  75. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/tenant/cli.rs +0 -0
  76. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/tenant/mod.rs +0 -0
  77. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/tenant/process.rs +0 -0
  78. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/tenant/render.rs +0 -0
  79. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/tenant/request.rs +0 -0
  80. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/tenant/tenant_client.rs +0 -0
  81. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/tenant/url_builder.rs +0 -0
  82. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/token/clear_smb_token.rs +0 -0
  83. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/token/get_smb_token.rs +0 -0
  84. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/token/is_logged_in.rs +0 -0
  85. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/token/mod.rs +0 -0
  86. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/token/smb_token_file_path.rs +0 -0
  87. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/ui/confirm_dialog.rs +0 -0
  88. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/ui/deployment_detail_view.rs +0 -0
  89. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/ui/deployment_table.rs +0 -0
  90. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/ui/me_view.rs +0 -0
  91. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/ui/mod.rs +0 -0
  92. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/ui/plain.rs +0 -0
  93. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/ui/project_detail_view.rs +0 -0
  94. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/ui/project_table.rs +0 -0
  95. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/ui/prompt.rs +0 -0
  96. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/ui/reporter.rs +0 -0
  97. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/cli/src/ui/theme.rs +0 -0
  98. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-auth/.gitignore +0 -0
  99. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-auth/README.md +0 -0
  100. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-auth/src/check_email.rs +0 -0
  101. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-auth/src/lib.rs +0 -0
  102. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-auth/src/login.rs +0 -0
  103. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-auth/src/logout.rs +0 -0
  104. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-auth/src/me.rs +0 -0
  105. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-auth/src/oauth/get_account_status.rs +0 -0
  106. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-auth/src/oauth/get_consent_url.rs +0 -0
  107. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-auth/src/oauth/get_profile.rs +0 -0
  108. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-auth/src/oauth/get_token.rs +0 -0
  109. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-auth/src/oauth/mod.rs +0 -0
  110. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-auth/src/oauth/provider.rs +0 -0
  111. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-auth/src/remove.rs +0 -0
  112. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-auth/src/resend_email_verification.rs +0 -0
  113. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-auth/src/resend_reset_password_instruction.rs +0 -0
  114. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-auth/src/reset_password.rs +0 -0
  115. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-auth/src/signup.rs +0 -0
  116. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-deploy/README.md +0 -0
  117. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-deploy/src/build.rs +0 -0
  118. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-deploy/src/error.rs +0 -0
  119. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-deploy/src/known_hosts.rs +0 -0
  120. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-deploy/src/lib.rs +0 -0
  121. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-deploy/src/report.rs +0 -0
  122. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-deploy/src/runner.rs +0 -0
  123. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-deploy/src/transport.rs +0 -0
  124. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-email-sdk/README.md +0 -0
  125. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-email-sdk/src/client.rs +0 -0
  126. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-email-sdk/src/client_credentials.rs +0 -0
  127. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-email-sdk/src/error.rs +0 -0
  128. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-email-sdk/src/message.rs +0 -0
  129. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-email-sdk/src/smbcloud_email_sdk.rs +0 -0
  130. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-mail/README.md +0 -0
  131. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-mail/src/mail_app.rs +0 -0
  132. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-mail/src/mail_inbox.rs +0 -0
  133. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-mail/src/mail_message.rs +0 -0
  134. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-mail/src/request.rs +0 -0
  135. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-mail/src/smbcloud_mail.rs +0 -0
  136. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-mail/src/url_builder.rs +0 -0
  137. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-model/README.md +0 -0
  138. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-model/src/account.rs +0 -0
  139. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-model/src/app_auth.rs +0 -0
  140. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-model/src/deploy_config_update.rs +0 -0
  141. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-model/src/error_codes.rs +0 -0
  142. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-model/src/forgot.rs +0 -0
  143. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-model/src/frontend_app.rs +0 -0
  144. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-model/src/lib.rs +0 -0
  145. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-model/src/login.rs +0 -0
  146. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-model/src/mail.rs +0 -0
  147. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-model/src/oauth.rs +0 -0
  148. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-model/src/repository.rs +0 -0
  149. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-model/src/reset_password_response.rs +0 -0
  150. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-model/src/runner.rs +0 -0
  151. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-model/src/signup.rs +0 -0
  152. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-model/src/tenant.rs +0 -0
  153. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-network/README.md +0 -0
  154. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-network/src/environment.rs +0 -0
  155. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-network/src/lib.rs +0 -0
  156. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-network/src/network.rs +0 -0
  157. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking/README.md +0 -0
  158. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking/src/constants.rs +0 -0
  159. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking/src/lib.rs +0 -0
  160. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking/src/smb_client.rs +0 -0
  161. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking-project/README.md +0 -0
  162. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking-project/src/crud_deploy_repo_create.rs +0 -0
  163. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking-project/src/crud_frontend_app_create.rs +0 -0
  164. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking-project/src/crud_frontend_app_deploy_config.rs +0 -0
  165. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking-project/src/crud_frontend_app_read.rs +0 -0
  166. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking-project/src/crud_frontend_app_update_deploy_config.rs +0 -0
  167. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking-project/src/crud_project_create.rs +0 -0
  168. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking-project/src/crud_project_delete.rs +0 -0
  169. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking-project/src/crud_project_deployment_create.rs +0 -0
  170. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking-project/src/crud_project_deployment_read.rs +0 -0
  171. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking-project/src/crud_project_deployment_update.rs +0 -0
  172. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking-project/src/crud_project_read.rs +0 -0
  173. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking-project/src/crud_project_update.rs +0 -0
  174. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking-project/src/lib.rs +0 -0
  175. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking-project/src/request.rs +0 -0
  176. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-networking-project/src/url_builder.rs +0 -0
  177. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-utils/README.md +0 -0
  178. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-utils/src/config.rs +0 -0
  179. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-utils/src/lib.rs +0 -0
  180. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/smbcloud-utils/src/write_config.rs +0 -0
  181. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/xcrs/README.md +0 -0
  182. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/xcrs/src/main.rs +0 -0
  183. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/xcrs/src/mcp.rs +0 -0
  184. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/crates/xcrs/src/xcrs.rs +0 -0
  185. {smbcloud_cli-0.6.2 → smbcloud_cli-0.7.0}/pyproject.toml +0 -0
@@ -2516,7 +2516,7 @@ checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
2516
2516
 
2517
2517
  [[package]]
2518
2518
  name = "smbcloud-auth"
2519
- version = "0.6.2"
2519
+ version = "0.7.0"
2520
2520
  dependencies = [
2521
2521
  "log",
2522
2522
  "reqwest",
@@ -2531,7 +2531,7 @@ dependencies = [
2531
2531
 
2532
2532
  [[package]]
2533
2533
  name = "smbcloud-auth-sdk"
2534
- version = "0.6.2"
2534
+ version = "0.7.0"
2535
2535
  dependencies = [
2536
2536
  "base64 0.22.1",
2537
2537
  "reqwest",
@@ -2546,7 +2546,7 @@ dependencies = [
2546
2546
 
2547
2547
  [[package]]
2548
2548
  name = "smbcloud-auth-sdk-py"
2549
- version = "0.6.2"
2549
+ version = "0.7.0"
2550
2550
  dependencies = [
2551
2551
  "pyo3",
2552
2552
  "pythonize",
@@ -2560,7 +2560,7 @@ dependencies = [
2560
2560
 
2561
2561
  [[package]]
2562
2562
  name = "smbcloud-auth-sdk-wasm"
2563
- version = "0.6.2"
2563
+ version = "0.7.0"
2564
2564
  dependencies = [
2565
2565
  "serde-wasm-bindgen",
2566
2566
  "smbcloud-auth-sdk",
@@ -2573,7 +2573,7 @@ dependencies = [
2573
2573
 
2574
2574
  [[package]]
2575
2575
  name = "smbcloud-cli"
2576
- version = "0.6.2"
2576
+ version = "0.7.0"
2577
2577
  dependencies = [
2578
2578
  "anyhow",
2579
2579
  "chrono",
@@ -2621,7 +2621,7 @@ dependencies = [
2621
2621
 
2622
2622
  [[package]]
2623
2623
  name = "smbcloud-deploy"
2624
- version = "0.6.2"
2624
+ version = "0.7.0"
2625
2625
  dependencies = [
2626
2626
  "anyhow",
2627
2627
  "serde_json",
@@ -2634,7 +2634,7 @@ dependencies = [
2634
2634
 
2635
2635
  [[package]]
2636
2636
  name = "smbcloud-email-sdk"
2637
- version = "0.6.2"
2637
+ version = "0.7.0"
2638
2638
  dependencies = [
2639
2639
  "anyhow",
2640
2640
  "log",
@@ -2649,7 +2649,7 @@ dependencies = [
2649
2649
 
2650
2650
  [[package]]
2651
2651
  name = "smbcloud-gresiq"
2652
- version = "0.6.2"
2652
+ version = "0.7.0"
2653
2653
  dependencies = [
2654
2654
  "native-tls",
2655
2655
  "postgres-native-tls",
@@ -2663,7 +2663,7 @@ dependencies = [
2663
2663
 
2664
2664
  [[package]]
2665
2665
  name = "smbcloud-gresiq-sdk"
2666
- version = "0.6.2"
2666
+ version = "0.7.0"
2667
2667
  dependencies = [
2668
2668
  "anyhow",
2669
2669
  "log",
@@ -2677,7 +2677,7 @@ dependencies = [
2677
2677
 
2678
2678
  [[package]]
2679
2679
  name = "smbcloud-mail"
2680
- version = "0.6.2"
2680
+ version = "0.7.0"
2681
2681
  dependencies = [
2682
2682
  "reqwest",
2683
2683
  "serde",
@@ -2689,7 +2689,7 @@ dependencies = [
2689
2689
 
2690
2690
  [[package]]
2691
2691
  name = "smbcloud-model"
2692
- version = "0.6.2"
2692
+ version = "0.7.0"
2693
2693
  dependencies = [
2694
2694
  "chrono",
2695
2695
  "log",
@@ -2705,7 +2705,7 @@ dependencies = [
2705
2705
 
2706
2706
  [[package]]
2707
2707
  name = "smbcloud-network"
2708
- version = "0.6.2"
2708
+ version = "0.7.0"
2709
2709
  dependencies = [
2710
2710
  "clap 4.6.6",
2711
2711
  "log",
@@ -2718,7 +2718,7 @@ dependencies = [
2718
2718
 
2719
2719
  [[package]]
2720
2720
  name = "smbcloud-networking"
2721
- version = "0.6.2"
2721
+ version = "0.7.0"
2722
2722
  dependencies = [
2723
2723
  "smbcloud-network",
2724
2724
  "url-builder",
@@ -2726,7 +2726,7 @@ dependencies = [
2726
2726
 
2727
2727
  [[package]]
2728
2728
  name = "smbcloud-networking-project"
2729
- version = "0.6.2"
2729
+ version = "0.7.0"
2730
2730
  dependencies = [
2731
2731
  "anyhow",
2732
2732
  "async-trait",
@@ -2744,11 +2744,11 @@ dependencies = [
2744
2744
 
2745
2745
  [[package]]
2746
2746
  name = "smbcloud-s6n"
2747
- version = "0.6.2"
2747
+ version = "0.7.0"
2748
2748
 
2749
2749
  [[package]]
2750
2750
  name = "smbcloud-utils"
2751
- version = "0.6.2"
2751
+ version = "0.7.0"
2752
2752
  dependencies = [
2753
2753
  "anyhow",
2754
2754
  "dirs",
@@ -3973,7 +3973,7 @@ checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
3973
3973
 
3974
3974
  [[package]]
3975
3975
  name = "xcrs"
3976
- version = "0.6.2"
3976
+ version = "0.7.0"
3977
3977
  dependencies = [
3978
3978
  "anyhow",
3979
3979
  "base64 0.22.1",
@@ -29,17 +29,17 @@ schemars = "1"
29
29
  serde = "1"
30
30
  serde_json = "1.0.82"
31
31
  serde_repr = "0.1"
32
- smbcloud-auth = { version = "0.6.2", path = "crates/smbcloud-auth" }
33
- smbcloud-auth-sdk = { version = "0.6.2", path = "crates/smbcloud-auth-sdk" }
34
- smbcloud-deploy = { version = "0.6.2", path = "crates/smbcloud-deploy" }
35
- smbcloud-email-sdk = { version = "0.6.2", path = "crates/smbcloud-email-sdk" }
36
- smbcloud-gresiq-sdk = { version = "0.6.2", path = "crates/smbcloud-gresiq-sdk" }
37
- smbcloud-mail = { version = "0.6.2", path = "crates/smbcloud-mail" }
38
- smbcloud-model = { version = "0.6.2", path = "crates/smbcloud-model" }
39
- smbcloud-network = { version = "0.6.2", path = "crates/smbcloud-network" }
40
- smbcloud-networking = { version = "0.6.2", path = "crates/smbcloud-networking" }
41
- smbcloud-networking-project = { version = "0.6.2", path = "crates/smbcloud-networking-project" }
42
- smbcloud-utils = { version = "0.6.2", path = "crates/smbcloud-utils" }
32
+ smbcloud-auth = { version = "0.7.0", path = "crates/smbcloud-auth" }
33
+ smbcloud-auth-sdk = { version = "0.7.0", path = "crates/smbcloud-auth-sdk" }
34
+ smbcloud-deploy = { version = "0.7.0", path = "crates/smbcloud-deploy" }
35
+ smbcloud-email-sdk = { version = "0.7.0", path = "crates/smbcloud-email-sdk" }
36
+ smbcloud-gresiq-sdk = { version = "0.7.0", path = "crates/smbcloud-gresiq-sdk" }
37
+ smbcloud-mail = { version = "0.7.0", path = "crates/smbcloud-mail" }
38
+ smbcloud-model = { version = "0.7.0", path = "crates/smbcloud-model" }
39
+ smbcloud-network = { version = "0.7.0", path = "crates/smbcloud-network" }
40
+ smbcloud-networking = { version = "0.7.0", path = "crates/smbcloud-networking" }
41
+ smbcloud-networking-project = { version = "0.7.0", path = "crates/smbcloud-networking-project" }
42
+ smbcloud-utils = { version = "0.7.0", path = "crates/smbcloud-utils" }
43
43
  spinners = "4.1.0"
44
44
  strum = "0.27"
45
45
  strum_macros = "0.27"
@@ -56,4 +56,4 @@ tsync = "2"
56
56
  uniffi = "0.31"
57
57
  url-builder = "0.1.1"
58
58
  wasm-bindgen = "0.2"
59
- xcrs = { version = "0.6.2", path = "crates/xcrs" }
59
+ xcrs = { version = "0.7.0", path = "crates/xcrs" }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: smbcloud-cli
3
- Version: 0.6.2
3
+ Version: 0.7.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "smbcloud-cli"
3
- version = "0.6.2"
3
+ version = "0.7.0"
4
4
  edition = "2021"
5
5
  authors = ["Seto Elkahfi <seto@smbcloud.xyz>"]
6
6
  description = "smbCloud command line interface."
@@ -38,6 +38,13 @@ const DEFAULT_RUST_TARGET: &str = "x86_64-unknown-linux-gnu";
38
38
  /// - `source` — local crate directory (defaults to current directory)
39
39
  /// - `binary_name` — binary filename to upload; falls back to Cargo package name
40
40
  /// - `rust_target` — local cross-compilation target triple; defaults to `x86_64-unknown-linux-gnu`
41
+ /// - `process_manager` — `"nohup"` (default) or `"systemd"`; systemd restarts
42
+ /// `<binary_name>.service` with `systemctl --user` (see `resolve_supervisor`)
43
+ /// - `port`, `health_path` — when both are set, the deploy polls
44
+ /// `http://127.0.0.1:<port><health_path>` after the restart
45
+ ///
46
+ /// The binary is uploaded as `<binary>.new`, the live one is kept as
47
+ /// `<binary>.previous`, and a failed check restores it.
41
48
  pub async fn process_deploy_rust(env: Environment, config: Config) -> Result<CommandResult> {
42
49
  let deploy_start = std::time::Instant::now();
43
50
 
@@ -56,6 +63,9 @@ pub async fn process_deploy_rust(env: Environment, config: Config) -> Result<Com
56
63
  ))));
57
64
  }
58
65
 
66
+ let supervisor = resolve_supervisor(config.project.process_manager.as_deref())?;
67
+ let health_url =
68
+ resolve_health_url(config.project.port, config.project.health_path.as_deref())?;
59
69
  let binary_name = resolve_binary_name(&config, source_dir)?;
60
70
  let rust_target = config
61
71
  .project
@@ -160,7 +170,10 @@ pub async fn process_deploy_rust(env: Environment, config: Config) -> Result<Com
160
170
  } else {
161
171
  format!("{}/", remote_path)
162
172
  };
163
- let destination = format!("git@{}:{}", rsync_host, remote_with_slash);
173
+ let destination = format!(
174
+ "git@{}:{}{}.new",
175
+ rsync_host, remote_with_slash, binary_name
176
+ );
164
177
  let binary_path_str = binary_path.to_string_lossy().into_owned();
165
178
 
166
179
  let binary_size = fs::metadata(&binary_path).map(|m| m.len()).unwrap_or(0);
@@ -229,7 +242,8 @@ pub async fn process_deploy_rust(env: Environment, config: Config) -> Result<Com
229
242
  ),
230
243
  );
231
244
 
232
- let deploy_script = build_remote_start_script(remote_path, &binary_name);
245
+ let deploy_script =
246
+ build_remote_start_script(remote_path, &binary_name, supervisor, health_url.as_deref());
233
247
  let ssh_output = run_remote_script(
234
248
  &identity_file_str,
235
249
  &known_hosts_file,
@@ -247,6 +261,12 @@ pub async fn process_deploy_rust(env: Environment, config: Config) -> Result<Com
247
261
  fail_message("Launch failed")
248
262
  );
249
263
  print_output_details(&ssh_output);
264
+ if let Some(line) = String::from_utf8_lossy(&ssh_output.stdout)
265
+ .lines()
266
+ .find(|line| line.starts_with("Rolled back"))
267
+ {
268
+ eprintln!("{}", line);
269
+ }
250
270
  mark_failed(
251
271
  &deploy_ref,
252
272
  &created_deployment,
@@ -474,9 +494,71 @@ echo "Prepared $APP_PATH"
474
494
  )
475
495
  }
476
496
 
477
- fn build_remote_start_script(remote_path: &str, binary_name: &str) -> String {
478
- format!(
497
+ /// How the service is stopped and started on the server.
498
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
499
+ enum Supervisor {
500
+ /// Kill the old process, start the new one with `nohup`.
501
+ Nohup,
502
+ /// `systemctl --user restart <binary_name>.service`.
503
+ Systemd,
504
+ }
505
+
506
+ /// Maps `process_manager` to a [`Supervisor`] (matched case-insensitively).
507
+ /// `None`, `""` and `"nohup"` keep the original behaviour. Any other value is a
508
+ /// hard error: a typo like `system` must not start a nohup process next to a
509
+ /// systemd-owned one that already holds the port.
510
+ fn resolve_supervisor(process_manager: Option<&str>) -> Result<Supervisor> {
511
+ match process_manager.map(str::trim) {
512
+ None | Some("") => Ok(Supervisor::Nohup),
513
+ Some(m) if m.eq_ignore_ascii_case("nohup") => Ok(Supervisor::Nohup),
514
+ Some(m) if m.eq_ignore_ascii_case("systemd") => Ok(Supervisor::Systemd),
515
+ Some(other) => Err(anyhow!(fail_message(&format!(
516
+ "Unknown process_manager '{other}' in .smb/config.toml. \
517
+ Expected \"nohup\" (default) or \"systemd\"."
518
+ )))),
519
+ }
520
+ }
521
+
522
+ /// Builds the URL the remote script polls after the restart. `None` unless both
523
+ /// `port` and `health_path` are set. The path ends up in a shell script, so it
524
+ /// is limited to URL-path characters, and every `%` must start a valid
525
+ /// percent-escape.
526
+ fn resolve_health_url(port: Option<u16>, health_path: Option<&str>) -> Result<Option<String>> {
527
+ let Some(path) = health_path.map(str::trim).filter(|path| !path.is_empty()) else {
528
+ return Ok(None);
529
+ };
530
+ let bytes = path.as_bytes();
531
+ let valid = path.starts_with('/')
532
+ && path
533
+ .chars()
534
+ .all(|c| c.is_ascii_alphanumeric() || matches!(c, '/' | '-' | '_' | '.' | '~' | '%'))
535
+ && bytes.iter().enumerate().all(|(i, &b)| {
536
+ b != b'%'
537
+ || bytes
538
+ .get(i + 1..i + 3)
539
+ .is_some_and(|hex| hex.iter().all(u8::is_ascii_hexdigit))
540
+ });
541
+ if !valid {
542
+ return Err(anyhow!(fail_message(&format!(
543
+ "Invalid health_path '{path}' in .smb/config.toml. \
544
+ It must start with '/' and contain only letters, digits and / - _ . ~ %; \
545
+ each % must be followed by two hex digits."
546
+ ))));
547
+ }
548
+ Ok(port.map(|port| format!("http://127.0.0.1:{port}{path}")))
549
+ }
550
+
551
+ /// Remote script: swap the uploaded `<binary>.new` into place (keeping the live
552
+ /// one as `<binary>.previous`), restart, verify, and roll back on failure.
553
+ fn build_remote_start_script(
554
+ remote_path: &str,
555
+ binary_name: &str,
556
+ supervisor: Supervisor,
557
+ health_url: Option<&str>,
558
+ ) -> String {
559
+ let mut script = format!(
479
560
  r#"set -e
561
+ exec 2>&1
480
562
  APP_PATH={remote_path}
481
563
  PROCESS_NAME={binary_name}
482
564
 
@@ -492,41 +574,166 @@ fi
492
574
 
493
575
  cd "$APP_PATH"
494
576
 
495
- if [ ! -f "$PROCESS_NAME" ]; then
496
- echo "Error: $PROCESS_NAME does not exist in $APP_PATH."
577
+ if [ ! -f "$PROCESS_NAME.new" ]; then
578
+ echo "Error: $PROCESS_NAME.new does not exist in $APP_PATH."
497
579
  exit 1
498
580
  fi
499
581
 
500
- chmod +x "$PROCESS_NAME"
501
-
502
- PID=$(pidof "$PROCESS_NAME" 2>/dev/null || true)
582
+ "#,
583
+ remote_path = shell_single_quote(remote_path),
584
+ binary_name = shell_single_quote(binary_name),
585
+ );
586
+ script.push_str(&restart_section(supervisor));
587
+ script.push_str(&verify_section(supervisor, health_url));
588
+ script.push_str(swap_section());
589
+ script.push_str(
590
+ r#"if restart_service && check_service; then
591
+ started
592
+ echo "Done."
593
+ exit 0
594
+ fi
503
595
 
504
- if [ -n "$PID" ]; then
505
- echo "Stopping $PROCESS_NAME ($PID)..."
506
- kill "$PID" 2>/dev/null || true
507
- sleep 2
508
- if kill -0 "$PID" 2>/dev/null; then
509
- echo "Force-killing $PROCESS_NAME ($PID)..."
510
- kill -9 "$PID" 2>/dev/null || true
596
+ echo "Error: $PROCESS_NAME failed its check after the restart."
597
+ show_logs
598
+ if [ -f "$PROCESS_NAME.previous" ]; then
599
+ echo "Rolling back to the previous binary..."
600
+ cp -p "$PROCESS_NAME.previous" "$PROCESS_NAME.rollback"
601
+ mv -f "$PROCESS_NAME.rollback" "$PROCESS_NAME"
602
+ if restart_service && check_service; then
603
+ echo "Rolled back to the previous $PROCESS_NAME, which is serving again."
604
+ else
605
+ echo "Rolled back to the previous $PROCESS_NAME, but it failed its check too."
511
606
  fi
607
+ else
608
+ echo "No previous binary to roll back to."
609
+ fi
610
+ exit 1
611
+ "#,
612
+ );
613
+ script
614
+ }
615
+
616
+ /// Keeps the live binary as `.previous` and moves `.new` into place.
617
+ fn swap_section() -> &'static str {
618
+ r#"if [ -f "$PROCESS_NAME" ]; then
619
+ cp -p "$PROCESS_NAME" "$PROCESS_NAME.previous"
512
620
  fi
621
+ chmod +x "$PROCESS_NAME.new"
622
+ mv -f "$PROCESS_NAME.new" "$PROCESS_NAME"
513
623
 
514
- echo "Starting $PROCESS_NAME..."
515
- nohup "./$PROCESS_NAME" >> "$APP_PATH/$PROCESS_NAME.log" 2>&1 &
516
- sleep 1
624
+ "#
625
+ }
626
+
627
+ /// Defines `restart_service`. The systemd variant also fails early, before the
628
+ /// swap, when the unit does not exist.
629
+ fn restart_section(supervisor: Supervisor) -> String {
630
+ match supervisor {
631
+ Supervisor::Nohup => r#"restart_service() {
632
+ PID=$(pidof "$PROCESS_NAME" 2>/dev/null || true)
633
+ if [ -n "$PID" ]; then
634
+ echo "Stopping $PROCESS_NAME ($PID)..."
635
+ kill "$PID" 2>/dev/null || true
636
+ sleep 2
637
+ if kill -0 "$PID" 2>/dev/null; then
638
+ echo "Force-killing $PROCESS_NAME ($PID)..."
639
+ kill -9 "$PID" 2>/dev/null || true
640
+ fi
641
+ fi
642
+ echo "Starting $PROCESS_NAME..."
643
+ nohup "./$PROCESS_NAME" >> "$APP_PATH/$PROCESS_NAME.log" 2>&1 &
644
+ }
517
645
 
518
- NEW_PID=$(pidof "$PROCESS_NAME" 2>/dev/null || true)
519
- if [ -z "$NEW_PID" ]; then
520
- echo "Error: failed to start $PROCESS_NAME."
646
+ "#
647
+ .to_owned(),
648
+ Supervisor::Systemd => r#"export XDG_RUNTIME_DIR="/run/user/$(id -u)"
649
+ UNIT="$PROCESS_NAME.service"
650
+ if ! systemctl --user cat "$UNIT" >/dev/null 2>&1; then
651
+ echo "Error: systemd --user unit $UNIT not found on the server."
652
+ echo "Create ~/.config/systemd/user/$UNIT and enable it (see the"
653
+ echo "smbcloud-deploy-rust guide, 'Process manager: systemd --user'),"
654
+ echo "or unset process_manager to use the default start. Then re-deploy."
521
655
  exit 1
522
656
  fi
523
657
 
524
- echo "Started $PROCESS_NAME as $NEW_PID"
525
- echo "Done."
658
+ restart_service() {
659
+ echo "Restarting $UNIT via systemd --user..."
660
+ systemctl --user restart "$UNIT"
661
+ }
662
+
663
+ "#
664
+ .to_owned(),
665
+ }
666
+ }
667
+
668
+ /// Defines `check_service`, `started` and `show_logs`. The check polls the
669
+ /// health URL for up to 30 seconds when there is one, else looks for a live
670
+ /// process (`is-active` under systemd, `pidof` under nohup).
671
+ fn verify_section(supervisor: Supervisor, health_url: Option<&str>) -> String {
672
+ let mut section = String::new();
673
+ match health_url {
674
+ Some(url) => {
675
+ section.push_str(&format!("HEALTH_URL={}\n\n", shell_single_quote(url)));
676
+ section.push_str(
677
+ r#"check_service() {
678
+ echo "Waiting for $HEALTH_URL..."
679
+ i=0
680
+ while [ "$i" -lt 30 ]; do
681
+ if curl -fsS -o /dev/null --max-time 2 "$HEALTH_URL"; then
682
+ return 0
683
+ fi
684
+ i=$((i + 1))
685
+ sleep 1
686
+ done
687
+ return 1
688
+ }
689
+
526
690
  "#,
527
- remote_path = shell_single_quote(remote_path),
528
- binary_name = shell_single_quote(binary_name),
529
- )
691
+ );
692
+ }
693
+ None => section.push_str(match supervisor {
694
+ Supervisor::Systemd => {
695
+ r#"check_service() {
696
+ sleep 3
697
+ systemctl --user is-active "$UNIT" >/dev/null 2>&1
698
+ }
699
+
700
+ "#
701
+ }
702
+ Supervisor::Nohup => {
703
+ r#"check_service() {
704
+ sleep 1
705
+ [ -n "$(pidof "$PROCESS_NAME" 2>/dev/null || true)" ]
706
+ }
707
+
708
+ "#
709
+ }
710
+ }),
711
+ }
712
+ section.push_str(match supervisor {
713
+ Supervisor::Systemd => {
714
+ r#"started() {
715
+ echo "Started $PROCESS_NAME via $UNIT"
716
+ }
717
+
718
+ show_logs() {
719
+ journalctl --user -u "$UNIT" -n 20 --no-pager 2>&1 || true
720
+ }
721
+
722
+ "#
723
+ }
724
+ Supervisor::Nohup => {
725
+ r#"started() {
726
+ echo "Started $PROCESS_NAME as $(pidof "$PROCESS_NAME" 2>/dev/null || true)"
727
+ }
728
+
729
+ show_logs() {
730
+ tail -n 20 "$APP_PATH/$PROCESS_NAME.log" 2>&1 || true
731
+ }
732
+
733
+ "#
734
+ }
735
+ });
736
+ section
530
737
  }
531
738
 
532
739
  fn build_ssh_command(identity_file: &str, known_hosts_file: &NamedTempFile) -> String {
@@ -639,3 +846,147 @@ async fn mark_failed(
639
846
  .await;
640
847
  }
641
848
  }
849
+
850
+ #[cfg(test)]
851
+ mod tests {
852
+ use super::*;
853
+
854
+ fn script(supervisor: Supervisor, health_url: Option<&str>) -> String {
855
+ build_remote_start_script("apps/my-app", "my-app", supervisor, health_url)
856
+ }
857
+
858
+ #[test]
859
+ fn resolve_supervisor_defaults_to_nohup() {
860
+ for pm in [
861
+ None,
862
+ Some(""),
863
+ Some("nohup"),
864
+ Some("NoHup"),
865
+ Some(" nohup "),
866
+ ] {
867
+ assert_eq!(resolve_supervisor(pm).unwrap(), Supervisor::Nohup, "{pm:?}");
868
+ }
869
+ }
870
+
871
+ #[test]
872
+ fn resolve_supervisor_systemd_is_case_insensitive() {
873
+ for pm in ["systemd", "SystemD", " systemd "] {
874
+ assert_eq!(resolve_supervisor(Some(pm)).unwrap(), Supervisor::Systemd);
875
+ }
876
+ }
877
+
878
+ #[test]
879
+ fn resolve_supervisor_rejects_unknown_value() {
880
+ for pm in ["system", "pm2", "supervisord"] {
881
+ let msg = resolve_supervisor(Some(pm)).unwrap_err().to_string();
882
+ assert!(msg.contains(pm), "names the offending value: {msg}");
883
+ assert!(msg.contains("nohup") && msg.contains("systemd"), "{msg}");
884
+ }
885
+ }
886
+
887
+ #[test]
888
+ fn systemd_script_restarts_the_user_unit() {
889
+ let s = script(Supervisor::Systemd, None);
890
+ assert!(s.contains("systemctl --user restart \"$UNIT\""));
891
+ assert!(s.contains("UNIT=\"$PROCESS_NAME.service\""));
892
+ assert!(s.contains("XDG_RUNTIME_DIR=\"/run/user/$(id -u)\""));
893
+ assert!(s.contains("systemctl --user is-active"));
894
+ assert!(!s.contains("nohup"));
895
+ assert!(!s.contains("kill"));
896
+ assert!(!s.contains("sudo"));
897
+ }
898
+
899
+ #[test]
900
+ fn nohup_script_keeps_the_pidof_flow() {
901
+ let s = script(Supervisor::Nohup, None);
902
+ assert!(s.contains("pidof"));
903
+ assert!(s.contains("nohup \"./$PROCESS_NAME\""));
904
+ assert!(!s.contains("systemctl"));
905
+ }
906
+
907
+ #[test]
908
+ fn both_scripts_back_up_and_swap_before_restarting() {
909
+ for supervisor in [Supervisor::Nohup, Supervisor::Systemd] {
910
+ let s = script(supervisor, None);
911
+ let backup = s
912
+ .find("cp -p \"$PROCESS_NAME\" \"$PROCESS_NAME.previous\"")
913
+ .unwrap();
914
+ let swap = s
915
+ .find("mv -f \"$PROCESS_NAME.new\" \"$PROCESS_NAME\"")
916
+ .unwrap();
917
+ let restart = s.find("if restart_service && check_service").unwrap();
918
+ assert!(backup < swap && swap < restart, "{supervisor:?}");
919
+ assert!(s.contains("Rolled back"), "{supervisor:?}");
920
+ }
921
+ }
922
+
923
+ #[test]
924
+ fn health_url_controls_the_curl_poll() {
925
+ for supervisor in [Supervisor::Nohup, Supervisor::Systemd] {
926
+ let with = script(supervisor, Some("http://127.0.0.1:8080/health"));
927
+ assert!(with.contains("curl -fsS"));
928
+ assert!(with.contains("HEALTH_URL='http://127.0.0.1:8080/health'"));
929
+ assert!(!script(supervisor, None).contains("curl"));
930
+ }
931
+ }
932
+
933
+ #[test]
934
+ fn health_url_needs_both_port_and_path() {
935
+ let url = resolve_health_url(Some(8080), Some("/health")).unwrap();
936
+ assert_eq!(url.as_deref(), Some("http://127.0.0.1:8080/health"));
937
+ assert_eq!(resolve_health_url(None, Some("/health")).unwrap(), None);
938
+ assert_eq!(resolve_health_url(Some(8080), None).unwrap(), None);
939
+ assert_eq!(resolve_health_url(Some(8080), Some("")).unwrap(), None);
940
+ }
941
+
942
+ #[test]
943
+ fn health_path_validation_rejects_unsafe_values() {
944
+ for path in [
945
+ "health", "/a b", "/x;rm", "/$(id)", "/a'b", "/foo%ZZ", "/foo%", "/foo%2",
946
+ ] {
947
+ assert!(
948
+ resolve_health_url(Some(8080), Some(path)).is_err(),
949
+ "{path}"
950
+ );
951
+ }
952
+ }
953
+
954
+ #[test]
955
+ fn health_path_accepts_valid_percent_escapes() {
956
+ let url = resolve_health_url(Some(8080), Some("/a%20b")).unwrap();
957
+ assert_eq!(url.as_deref(), Some("http://127.0.0.1:8080/a%20b"));
958
+ }
959
+
960
+ #[test]
961
+ fn binary_name_with_single_quote_is_quoted() {
962
+ let s = build_remote_start_script("apps/x", "a'b", Supervisor::Nohup, None);
963
+ assert!(s.contains("PROCESS_NAME='a'\"'\"'b'"));
964
+ }
965
+
966
+ #[test]
967
+ fn every_script_variant_is_valid_bash() {
968
+ for supervisor in [Supervisor::Nohup, Supervisor::Systemd] {
969
+ for health in [None, Some("http://127.0.0.1:8080/health")] {
970
+ let mut child = Command::new("bash")
971
+ .args(["-n", "-s"])
972
+ .stdin(Stdio::piped())
973
+ .stdout(Stdio::piped())
974
+ .stderr(Stdio::piped())
975
+ .spawn()
976
+ .expect("bash is available");
977
+ child
978
+ .stdin
979
+ .take()
980
+ .unwrap()
981
+ .write_all(script(supervisor, health).as_bytes())
982
+ .unwrap();
983
+ let out = child.wait_with_output().unwrap();
984
+ assert!(
985
+ out.status.success(),
986
+ "{supervisor:?} {health:?}: {}",
987
+ String::from_utf8_lossy(&out.stderr)
988
+ );
989
+ }
990
+ }
991
+ }
992
+ }
@@ -200,6 +200,7 @@ fn strip_project(project: &Project) -> Project {
200
200
  pm2_env: None,
201
201
  process_manager: None,
202
202
  port: None,
203
+ health_path: None,
203
204
  shared_lib: None,
204
205
  compile_cmd: None,
205
206
  install_command: None,
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "smbcloud-auth"
3
- version = "0.6.2"
3
+ version = "0.7.0"
4
4
  edition = "2024"
5
5
  authors = ["Seto Elkahfi <seto@smbcloud.xyz>"]
6
6
  description = "Cross-platform Auth SDK for the smbCloud platform."