secator 0.18.0__tar.gz → 0.19.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.

Potentially problematic release.


This version of secator might be problematic. Click here for more details.

Files changed (213) hide show
  1. {secator-0.18.0 → secator-0.19.0}/CHANGELOG.md +7 -0
  2. {secator-0.18.0 → secator-0.19.0}/PKG-INFO +1 -1
  3. {secator-0.18.0 → secator-0.19.0}/pyproject.toml +1 -1
  4. {secator-0.18.0 → secator-0.19.0}/secator/config.py +2 -1
  5. {secator-0.18.0 → secator-0.19.0}/secator/installer.py +1 -1
  6. {secator-0.18.0 → secator-0.19.0}/secator/output_types/stat.py +8 -3
  7. {secator-0.18.0 → secator-0.19.0}/secator/runners/_base.py +0 -2
  8. {secator-0.18.0 → secator-0.19.0}/secator/runners/command.py +131 -13
  9. {secator-0.18.0 → secator-0.19.0}/.coderabbit.yaml +0 -0
  10. {secator-0.18.0 → secator-0.19.0}/.docker/Dockerfile.alpine +0 -0
  11. {secator-0.18.0 → secator-0.19.0}/.docker/Dockerfile.arch +0 -0
  12. {secator-0.18.0 → secator-0.19.0}/.docker/Dockerfile.debian +0 -0
  13. {secator-0.18.0 → secator-0.19.0}/.docker/Dockerfile.kali +0 -0
  14. {secator-0.18.0 → secator-0.19.0}/.docker/Dockerfile.osx +0 -0
  15. {secator-0.18.0 → secator-0.19.0}/.docker/Dockerfile.ubuntu +0 -0
  16. {secator-0.18.0 → secator-0.19.0}/.docker/build_all.sh +0 -0
  17. {secator-0.18.0 → secator-0.19.0}/.dockerignore +0 -0
  18. {secator-0.18.0 → secator-0.19.0}/.flake8 +0 -0
  19. {secator-0.18.0 → secator-0.19.0}/.gitignore +0 -0
  20. {secator-0.18.0 → secator-0.19.0}/CONTRIBUTING.md +0 -0
  21. {secator-0.18.0 → secator-0.19.0}/Dockerfile +0 -0
  22. {secator-0.18.0 → secator-0.19.0}/LICENSE +0 -0
  23. {secator-0.18.0 → secator-0.19.0}/README.md +0 -0
  24. {secator-0.18.0 → secator-0.19.0}/SECURITY.md +0 -0
  25. {secator-0.18.0 → secator-0.19.0}/cloudbuild.yaml +0 -0
  26. {secator-0.18.0 → secator-0.19.0}/helm/.helmignore +0 -0
  27. {secator-0.18.0 → secator-0.19.0}/helm/Chart.yaml +0 -0
  28. {secator-0.18.0 → secator-0.19.0}/helm/templates/redis-service.yaml +0 -0
  29. {secator-0.18.0 → secator-0.19.0}/helm/templates/redis.yaml +0 -0
  30. {secator-0.18.0 → secator-0.19.0}/helm/templates/secator-manager.yaml +0 -0
  31. {secator-0.18.0 → secator-0.19.0}/helm/templates/secator-worker.yaml +0 -0
  32. {secator-0.18.0 → secator-0.19.0}/helm/values.yaml +0 -0
  33. {secator-0.18.0 → secator-0.19.0}/scripts/download_cves.sh +0 -0
  34. {secator-0.18.0 → secator-0.19.0}/scripts/generate_tools_md_table.py +0 -0
  35. {secator-0.18.0 → secator-0.19.0}/scripts/install.sh +0 -0
  36. {secator-0.18.0 → secator-0.19.0}/scripts/install_asciinema.sh +0 -0
  37. {secator-0.18.0 → secator-0.19.0}/scripts/install_go.sh +0 -0
  38. {secator-0.18.0 → secator-0.19.0}/scripts/install_ruby.sh +0 -0
  39. {secator-0.18.0 → secator-0.19.0}/scripts/msf/exploit_cve.rc +0 -0
  40. {secator-0.18.0 → secator-0.19.0}/scripts/msf/ftp_anonymous.rc +0 -0
  41. {secator-0.18.0 → secator-0.19.0}/scripts/msf/ftp_version.rc +0 -0
  42. {secator-0.18.0 → secator-0.19.0}/scripts/msf/ftp_vsftpd_234_backdoor.rc +0 -0
  43. {secator-0.18.0 → secator-0.19.0}/scripts/msf/redis.rc +0 -0
  44. {secator-0.18.0 → secator-0.19.0}/scripts/stories/STORY.md +0 -0
  45. {secator-0.18.0 → secator-0.19.0}/scripts/stories/aliases.sh +0 -0
  46. {secator-0.18.0 → secator-0.19.0}/scripts/stories/demo.sh +0 -0
  47. {secator-0.18.0 → secator-0.19.0}/scripts/stories/fmt.sh +0 -0
  48. {secator-0.18.0 → secator-0.19.0}/scripts/stories/input.sh +0 -0
  49. {secator-0.18.0 → secator-0.19.0}/scripts/stories/pipe.sh +0 -0
  50. {secator-0.18.0 → secator-0.19.0}/scripts/stories/short_demo.sh +0 -0
  51. {secator-0.18.0 → secator-0.19.0}/scripts/update_tools.sh +0 -0
  52. {secator-0.18.0 → secator-0.19.0}/secator/.gitignore +0 -0
  53. {secator-0.18.0 → secator-0.19.0}/secator/__init__.py +0 -0
  54. {secator-0.18.0 → secator-0.19.0}/secator/celery.py +0 -0
  55. {secator-0.18.0 → secator-0.19.0}/secator/celery_signals.py +0 -0
  56. {secator-0.18.0 → secator-0.19.0}/secator/celery_utils.py +0 -0
  57. {secator-0.18.0 → secator-0.19.0}/secator/cli.py +0 -0
  58. {secator-0.18.0 → secator-0.19.0}/secator/cli_helper.py +0 -0
  59. {secator-0.18.0 → secator-0.19.0}/secator/click.py +0 -0
  60. {secator-0.18.0 → secator-0.19.0}/secator/configs/__init__.py +0 -0
  61. {secator-0.18.0 → secator-0.19.0}/secator/configs/profiles/__init__.py +0 -0
  62. {secator-0.18.0 → secator-0.19.0}/secator/configs/profiles/aggressive.yaml +0 -0
  63. {secator-0.18.0 → secator-0.19.0}/secator/configs/profiles/http_headless.yaml +0 -0
  64. {secator-0.18.0 → secator-0.19.0}/secator/configs/profiles/http_record.yaml +0 -0
  65. {secator-0.18.0 → secator-0.19.0}/secator/configs/profiles/insane.yaml +0 -0
  66. {secator-0.18.0 → secator-0.19.0}/secator/configs/profiles/paranoid.yaml +0 -0
  67. {secator-0.18.0 → secator-0.19.0}/secator/configs/profiles/polite.yaml +0 -0
  68. {secator-0.18.0 → secator-0.19.0}/secator/configs/profiles/sneaky.yaml +0 -0
  69. {secator-0.18.0 → secator-0.19.0}/secator/configs/profiles/tor.yaml +0 -0
  70. {secator-0.18.0 → secator-0.19.0}/secator/configs/scans/__init__.py +0 -0
  71. {secator-0.18.0 → secator-0.19.0}/secator/configs/scans/domain.yaml +0 -0
  72. {secator-0.18.0 → secator-0.19.0}/secator/configs/scans/host.yaml +0 -0
  73. {secator-0.18.0 → secator-0.19.0}/secator/configs/scans/network.yaml +0 -0
  74. {secator-0.18.0 → secator-0.19.0}/secator/configs/scans/subdomain.yaml +0 -0
  75. {secator-0.18.0 → secator-0.19.0}/secator/configs/scans/url.yaml +0 -0
  76. {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/__init__.py +0 -0
  77. {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/cidr_recon.yaml +0 -0
  78. {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/code_scan.yaml +0 -0
  79. {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/host_recon.yaml +0 -0
  80. {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/subdomain_recon.yaml +0 -0
  81. {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/url_bypass.yaml +0 -0
  82. {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/url_crawl.yaml +0 -0
  83. {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/url_dirsearch.yaml +0 -0
  84. {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/url_fuzz.yaml +0 -0
  85. {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/url_params_fuzz.yaml +0 -0
  86. {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/url_vuln.yaml +0 -0
  87. {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/user_hunt.yaml +0 -0
  88. {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/wordpress.yaml +0 -0
  89. {secator-0.18.0 → secator-0.19.0}/secator/cve.py +0 -0
  90. {secator-0.18.0 → secator-0.19.0}/secator/decorators.py +0 -0
  91. {secator-0.18.0 → secator-0.19.0}/secator/definitions.py +0 -0
  92. {secator-0.18.0 → secator-0.19.0}/secator/exporters/__init__.py +0 -0
  93. {secator-0.18.0 → secator-0.19.0}/secator/exporters/_base.py +0 -0
  94. {secator-0.18.0 → secator-0.19.0}/secator/exporters/console.py +0 -0
  95. {secator-0.18.0 → secator-0.19.0}/secator/exporters/csv.py +0 -0
  96. {secator-0.18.0 → secator-0.19.0}/secator/exporters/gdrive.py +0 -0
  97. {secator-0.18.0 → secator-0.19.0}/secator/exporters/json.py +0 -0
  98. {secator-0.18.0 → secator-0.19.0}/secator/exporters/table.py +0 -0
  99. {secator-0.18.0 → secator-0.19.0}/secator/exporters/txt.py +0 -0
  100. {secator-0.18.0 → secator-0.19.0}/secator/hooks/__init__.py +0 -0
  101. {secator-0.18.0 → secator-0.19.0}/secator/hooks/gcs.py +0 -0
  102. {secator-0.18.0 → secator-0.19.0}/secator/hooks/mongodb.py +0 -0
  103. {secator-0.18.0 → secator-0.19.0}/secator/loader.py +0 -0
  104. {secator-0.18.0 → secator-0.19.0}/secator/output_types/__init__.py +0 -0
  105. {secator-0.18.0 → secator-0.19.0}/secator/output_types/_base.py +0 -0
  106. {secator-0.18.0 → secator-0.19.0}/secator/output_types/certificate.py +0 -0
  107. {secator-0.18.0 → secator-0.19.0}/secator/output_types/error.py +0 -0
  108. {secator-0.18.0 → secator-0.19.0}/secator/output_types/exploit.py +0 -0
  109. {secator-0.18.0 → secator-0.19.0}/secator/output_types/info.py +0 -0
  110. {secator-0.18.0 → secator-0.19.0}/secator/output_types/ip.py +0 -0
  111. {secator-0.18.0 → secator-0.19.0}/secator/output_types/port.py +0 -0
  112. {secator-0.18.0 → secator-0.19.0}/secator/output_types/progress.py +0 -0
  113. {secator-0.18.0 → secator-0.19.0}/secator/output_types/record.py +0 -0
  114. {secator-0.18.0 → secator-0.19.0}/secator/output_types/state.py +0 -0
  115. {secator-0.18.0 → secator-0.19.0}/secator/output_types/subdomain.py +0 -0
  116. {secator-0.18.0 → secator-0.19.0}/secator/output_types/tag.py +0 -0
  117. {secator-0.18.0 → secator-0.19.0}/secator/output_types/target.py +0 -0
  118. {secator-0.18.0 → secator-0.19.0}/secator/output_types/url.py +0 -0
  119. {secator-0.18.0 → secator-0.19.0}/secator/output_types/user_account.py +0 -0
  120. {secator-0.18.0 → secator-0.19.0}/secator/output_types/vulnerability.py +0 -0
  121. {secator-0.18.0 → secator-0.19.0}/secator/output_types/warning.py +0 -0
  122. {secator-0.18.0 → secator-0.19.0}/secator/report.py +0 -0
  123. {secator-0.18.0 → secator-0.19.0}/secator/rich.py +0 -0
  124. {secator-0.18.0 → secator-0.19.0}/secator/runners/__init__.py +0 -0
  125. {secator-0.18.0 → secator-0.19.0}/secator/runners/_helpers.py +0 -0
  126. {secator-0.18.0 → secator-0.19.0}/secator/runners/celery.py +0 -0
  127. {secator-0.18.0 → secator-0.19.0}/secator/runners/scan.py +0 -0
  128. {secator-0.18.0 → secator-0.19.0}/secator/runners/task.py +0 -0
  129. {secator-0.18.0 → secator-0.19.0}/secator/runners/workflow.py +0 -0
  130. {secator-0.18.0 → secator-0.19.0}/secator/scans/__init__.py +0 -0
  131. {secator-0.18.0 → secator-0.19.0}/secator/serializers/__init__.py +0 -0
  132. {secator-0.18.0 → secator-0.19.0}/secator/serializers/dataclass.py +0 -0
  133. {secator-0.18.0 → secator-0.19.0}/secator/serializers/json.py +0 -0
  134. {secator-0.18.0 → secator-0.19.0}/secator/serializers/regex.py +0 -0
  135. {secator-0.18.0 → secator-0.19.0}/secator/tasks/__init__.py +0 -0
  136. {secator-0.18.0 → secator-0.19.0}/secator/tasks/_categories.py +0 -0
  137. {secator-0.18.0 → secator-0.19.0}/secator/tasks/arjun.py +0 -0
  138. {secator-0.18.0 → secator-0.19.0}/secator/tasks/bbot.py +0 -0
  139. {secator-0.18.0 → secator-0.19.0}/secator/tasks/bup.py +0 -0
  140. {secator-0.18.0 → secator-0.19.0}/secator/tasks/cariddi.py +0 -0
  141. {secator-0.18.0 → secator-0.19.0}/secator/tasks/dalfox.py +0 -0
  142. {secator-0.18.0 → secator-0.19.0}/secator/tasks/dirsearch.py +0 -0
  143. {secator-0.18.0 → secator-0.19.0}/secator/tasks/dnsx.py +0 -0
  144. {secator-0.18.0 → secator-0.19.0}/secator/tasks/feroxbuster.py +0 -0
  145. {secator-0.18.0 → secator-0.19.0}/secator/tasks/ffuf.py +0 -0
  146. {secator-0.18.0 → secator-0.19.0}/secator/tasks/fping.py +0 -0
  147. {secator-0.18.0 → secator-0.19.0}/secator/tasks/gau.py +0 -0
  148. {secator-0.18.0 → secator-0.19.0}/secator/tasks/gf.py +0 -0
  149. {secator-0.18.0 → secator-0.19.0}/secator/tasks/gitleaks.py +0 -0
  150. {secator-0.18.0 → secator-0.19.0}/secator/tasks/gospider.py +0 -0
  151. {secator-0.18.0 → secator-0.19.0}/secator/tasks/grype.py +0 -0
  152. {secator-0.18.0 → secator-0.19.0}/secator/tasks/h8mail.py +0 -0
  153. {secator-0.18.0 → secator-0.19.0}/secator/tasks/httpx.py +0 -0
  154. {secator-0.18.0 → secator-0.19.0}/secator/tasks/katana.py +0 -0
  155. {secator-0.18.0 → secator-0.19.0}/secator/tasks/maigret.py +0 -0
  156. {secator-0.18.0 → secator-0.19.0}/secator/tasks/mapcidr.py +0 -0
  157. {secator-0.18.0 → secator-0.19.0}/secator/tasks/msfconsole.py +0 -0
  158. {secator-0.18.0 → secator-0.19.0}/secator/tasks/naabu.py +0 -0
  159. {secator-0.18.0 → secator-0.19.0}/secator/tasks/nmap.py +0 -0
  160. {secator-0.18.0 → secator-0.19.0}/secator/tasks/nuclei.py +0 -0
  161. {secator-0.18.0 → secator-0.19.0}/secator/tasks/searchsploit.py +0 -0
  162. {secator-0.18.0 → secator-0.19.0}/secator/tasks/subfinder.py +0 -0
  163. {secator-0.18.0 → secator-0.19.0}/secator/tasks/testssl.py +0 -0
  164. {secator-0.18.0 → secator-0.19.0}/secator/tasks/trivy.py +0 -0
  165. {secator-0.18.0 → secator-0.19.0}/secator/tasks/wafw00f.py +0 -0
  166. {secator-0.18.0 → secator-0.19.0}/secator/tasks/wpprobe.py +0 -0
  167. {secator-0.18.0 → secator-0.19.0}/secator/tasks/wpscan.py +0 -0
  168. {secator-0.18.0 → secator-0.19.0}/secator/template.py +0 -0
  169. {secator-0.18.0 → secator-0.19.0}/secator/thread.py +0 -0
  170. {secator-0.18.0 → secator-0.19.0}/secator/tree.py +0 -0
  171. {secator-0.18.0 → secator-0.19.0}/secator/utils.py +0 -0
  172. {secator-0.18.0 → secator-0.19.0}/secator/utils_test.py +0 -0
  173. {secator-0.18.0 → secator-0.19.0}/secator/workflows/__init__.py +0 -0
  174. {secator-0.18.0 → secator-0.19.0}/tests/__init__.py +0 -0
  175. {secator-0.18.0 → secator-0.19.0}/tests/fixtures/h8mail_breach.txt +0 -0
  176. {secator-0.18.0 → secator-0.19.0}/tests/fixtures/ls.py +0 -0
  177. {secator-0.18.0 → secator-0.19.0}/tests/fixtures/msfconsole_input.rc +0 -0
  178. {secator-0.18.0 → secator-0.19.0}/tests/fixtures/nmap_output.xml +0 -0
  179. {secator-0.18.0 → secator-0.19.0}/tests/integration/__init__.py +0 -0
  180. {secator-0.18.0 → secator-0.19.0}/tests/integration/all.yaml +0 -0
  181. {secator-0.18.0 → secator-0.19.0}/tests/integration/inputs.py +0 -0
  182. {secator-0.18.0 → secator-0.19.0}/tests/integration/outputs.py +0 -0
  183. {secator-0.18.0 → secator-0.19.0}/tests/integration/setup.sh +0 -0
  184. {secator-0.18.0 → secator-0.19.0}/tests/integration/teardown.sh +0 -0
  185. {secator-0.18.0 → secator-0.19.0}/tests/integration/test_addons.py +0 -0
  186. {secator-0.18.0 → secator-0.19.0}/tests/integration/test_celery.py +0 -0
  187. {secator-0.18.0 → secator-0.19.0}/tests/integration/test_scans.py +0 -0
  188. {secator-0.18.0 → secator-0.19.0}/tests/integration/test_tasks.py +0 -0
  189. {secator-0.18.0 → secator-0.19.0}/tests/integration/test_tasks_categories.py +0 -0
  190. {secator-0.18.0 → secator-0.19.0}/tests/integration/test_worker.py +0 -0
  191. {secator-0.18.0 → secator-0.19.0}/tests/integration/test_workflows.py +0 -0
  192. {secator-0.18.0 → secator-0.19.0}/tests/integration/wordlist.txt +0 -0
  193. {secator-0.18.0 → secator-0.19.0}/tests/integration/wordlist_dns.txt +0 -0
  194. {secator-0.18.0 → secator-0.19.0}/tests/integration/wordpress_toolbox/Dockerfile +0 -0
  195. {secator-0.18.0 → secator-0.19.0}/tests/integration/wordpress_toolbox/Makefile +0 -0
  196. {secator-0.18.0 → secator-0.19.0}/tests/performance/__init__.py +0 -0
  197. {secator-0.18.0 → secator-0.19.0}/tests/performance/loadtester.py +0 -0
  198. {secator-0.18.0 → secator-0.19.0}/tests/performance/test_worker.py +0 -0
  199. {secator-0.18.0 → secator-0.19.0}/tests/template/test_templates.py +0 -0
  200. {secator-0.18.0 → secator-0.19.0}/tests/unit/__init__.py +0 -0
  201. {secator-0.18.0 → secator-0.19.0}/tests/unit/test_celery.py +0 -0
  202. {secator-0.18.0 → secator-0.19.0}/tests/unit/test_cli.py +0 -0
  203. {secator-0.18.0 → secator-0.19.0}/tests/unit/test_command.py +0 -0
  204. {secator-0.18.0 → secator-0.19.0}/tests/unit/test_config.py +0 -0
  205. {secator-0.18.0 → secator-0.19.0}/tests/unit/test_offline.py +0 -0
  206. {secator-0.18.0 → secator-0.19.0}/tests/unit/test_runners.py +0 -0
  207. {secator-0.18.0 → secator-0.19.0}/tests/unit/test_runners_helpers.py +0 -0
  208. {secator-0.18.0 → secator-0.19.0}/tests/unit/test_scans.py +0 -0
  209. {secator-0.18.0 → secator-0.19.0}/tests/unit/test_serializers.py +0 -0
  210. {secator-0.18.0 → secator-0.19.0}/tests/unit/test_tasks.py +0 -0
  211. {secator-0.18.0 → secator-0.19.0}/tests/unit/test_tasks_categories.py +0 -0
  212. {secator-0.18.0 → secator-0.19.0}/tests/unit/test_template.py +0 -0
  213. {secator-0.18.0 → secator-0.19.0}/tests/unit/test_utils.py +0 -0
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.19.0](https://github.com/freelabz/secator/compare/v0.18.0...v0.19.0) (2025-10-23)
4
+
5
+
6
+ ### Features
7
+
8
+ * add monitor thread ([#727](https://github.com/freelabz/secator/issues/727)) ([5377e77](https://github.com/freelabz/secator/commit/5377e77186c88b1331b7ddc4a5304a610ad3e253))
9
+
3
10
  ## [0.18.0](https://github.com/freelabz/secator/compare/v0.17.0...v0.18.0) (2025-10-22)
4
11
 
5
12
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: secator
3
- Version: 0.18.0
3
+ Version: 0.19.0
4
4
  Summary: The pentester's swiss knife.
5
5
  Project-URL: Homepage, https://github.com/freelabz/secator
6
6
  Project-URL: Issues, https://github.com/freelabz/secator/issues
@@ -4,7 +4,7 @@ build-backend = 'hatchling.build'
4
4
 
5
5
  [project]
6
6
  name = 'secator'
7
- version = "0.18.0"
7
+ version = "0.19.0"
8
8
  authors = [{ name = 'FreeLabz', email = 'sales@freelabz.com' }]
9
9
  readme = 'README.md'
10
10
  description = "The pentester's swiss knife."
@@ -67,6 +67,8 @@ class Celery(StrictModel):
67
67
  task_acks_late: bool = False
68
68
  task_send_sent_event: bool = False
69
69
  task_reject_on_worker_lost: bool = False
70
+ task_max_timeout: int = -1
71
+ task_memory_limit_mb: int = -1
70
72
  worker_max_tasks_per_child: int = 20
71
73
  worker_prefetch_multiplier: int = 1
72
74
  worker_send_task_events: bool = False
@@ -100,7 +102,6 @@ class Security(StrictModel):
100
102
  allow_local_file_access: bool = True
101
103
  auto_install_commands: bool = True
102
104
  force_source_install: bool = False
103
- memory_limit_mb: int = -1
104
105
 
105
106
 
106
107
  class HTTP(StrictModel):
@@ -150,7 +150,7 @@ class PackageInstaller:
150
150
 
151
151
  # Installer cmd
152
152
  cmd = distribution.pm_installer
153
- if CONFIG.security.autoinstall_commands and IN_CELERY_WORKER_PROCESS:
153
+ if CONFIG.security.auto_install_commands and IN_CELERY_WORKER_PROCESS:
154
154
  cmd = f'flock /tmp/install.lock {cmd}'
155
155
  if getpass.getuser() != 'root':
156
156
  cmd = f'sudo {cmd}'
@@ -11,6 +11,7 @@ class Stat(OutputType):
11
11
  pid: int
12
12
  cpu: int
13
13
  memory: int
14
+ memory_limit: int
14
15
  net_conns: int = field(default=None, repr=True)
15
16
  extra_data: dict = field(default_factory=dict)
16
17
  _source: str = field(default='', repr=True, compare=False)
@@ -26,11 +27,15 @@ class Stat(OutputType):
26
27
  _sort_by = ('name', 'pid')
27
28
 
28
29
  def __str__(self) -> str:
29
- return f'{self.name} [pid={self.pid}] [cpu={self.cpu:.2f}%] [memory={self.memory:.2f}%]'
30
+ return f'{self.name} ([bold]pid[/]:{self.pid}) ([bold]cpu[/]:{self.cpu:.2f}%) ([bold]memory[/]:{self.memory:.2f}MB / {self.memory_limit}MB)' # noqa: E501
30
31
 
31
32
  def __repr__(self) -> str:
32
- s = rf'[dim yellow3]📊 {self.name} \[pid={self.pid}] \[cpu={self.cpu:.2f}%] \[memory={self.memory:.2f}%]'
33
+ s = rf'[dim yellow3]📊 {self.name} ([bold]pid[/]:{self.pid}) ([bold]cpu[/]:{self.cpu:.2f}%)'
34
+ s += rf' ([bold]memory[/]:{self.memory:.2f}MB'
35
+ if self.memory_limit != -1:
36
+ s += rf' / {self.memory_limit}MB'
37
+ s += ')'
33
38
  if self.net_conns:
34
- s += rf' \[connections={self.net_conns}]'
39
+ s += rf' ([bold]connections[/]:{self.net_conns})'
35
40
  s += ' [/]'
36
41
  return rich_to_ansi(s)
@@ -788,8 +788,6 @@ class Runner:
788
788
  continue
789
789
  result = hook(self, *args)
790
790
  self.debug('hook success', obj={'name': hook_type, 'fun': fun}, sub=sub, verbose='item' in sub) # noqa: E501
791
- if isinstance(result, Error):
792
- self.add_result(result, hooks=False)
793
791
  except Exception as e:
794
792
  self.debug('hook failed', obj={'name': hook_type, 'fun': fun}, sub=sub) # noqa: E501
795
793
  error = Error.from_exception(e, message=f'Hook "{fun}" execution failed')
@@ -2,11 +2,13 @@ import copy
2
2
  import getpass
3
3
  import logging
4
4
  import os
5
+ import queue
5
6
  import re
6
7
  import shlex
7
8
  import signal
8
9
  import subprocess
9
10
  import sys
11
+ import threading
10
12
 
11
13
  from time import time
12
14
 
@@ -178,6 +180,13 @@ class Command(Runner):
178
180
  # Process
179
181
  self.process = None
180
182
 
183
+ # Monitor thread (lazy initialization)
184
+ self.monitor_thread = None
185
+ self.monitor_stop_event = None
186
+ self.monitor_queue = None
187
+ self.process_start_time = None
188
+ # self.retry_count = 0 # TODO: remove this
189
+
181
190
  # Sudo
182
191
  self.requires_sudo = False
183
192
 
@@ -205,6 +214,13 @@ class Command(Runner):
205
214
  item_loaders.append(instance_func)
206
215
  self.item_loaders = item_loaders
207
216
 
217
+ def _init_monitor_objects(self):
218
+ """Initialize monitor thread objects when needed (lazy initialization)."""
219
+ if self.monitor_stop_event is None:
220
+ self.monitor_stop_event = threading.Event()
221
+ if self.monitor_queue is None:
222
+ self.monitor_queue = queue.Queue()
223
+
208
224
  def toDict(self):
209
225
  res = super().toDict()
210
226
  res.update({
@@ -443,7 +459,7 @@ class Command(Runner):
443
459
  # Output and results
444
460
  self.return_code = 0
445
461
  self.killed = False
446
- self.memory_limit_mb = CONFIG.security.memory_limit_mb
462
+ self.memory_limit_mb = CONFIG.celery.task_memory_limit_mb
447
463
 
448
464
  # Run the command using subprocess
449
465
  env = os.environ
@@ -458,6 +474,13 @@ class Command(Runner):
458
474
  env=env,
459
475
  cwd=self.cwd)
460
476
 
477
+ # Initialize monitor objects and start monitor thread
478
+ self._init_monitor_objects()
479
+ self.process_start_time = time()
480
+ self.monitor_stop_event.clear()
481
+ self.monitor_thread = threading.Thread(target=self._monitor_process, daemon=True)
482
+ self.monitor_thread.start()
483
+
461
484
  # If sudo password is provided, send it to stdin
462
485
  if sudo_password:
463
486
  self.process.stdin.write(f"{sudo_password}\n")
@@ -469,6 +492,7 @@ class Command(Runner):
469
492
  if not line:
470
493
  break
471
494
  yield from self.process_line(line)
495
+ yield from self.process_monitor_queue()
472
496
 
473
497
  # Run hooks after cmd has completed successfully
474
498
  result = self.run_hooks('on_cmd_done', sub='end')
@@ -478,11 +502,6 @@ class Command(Runner):
478
502
  except FileNotFoundError as e:
479
503
  yield from self.handle_file_not_found(e)
480
504
 
481
- except MemoryError as e:
482
- self.debug(f'{self.unique_name}: {type(e).__name__}.', sub='end')
483
- self.stop_process(exit_ok=True, sig=signal.SIGTERM)
484
- yield Warning(message=f'Memory limit {self.memory_limit_mb}MB reached for {self.unique_name}')
485
-
486
505
  except BaseException as e:
487
506
  self.debug(f'{self.unique_name}: {type(e).__name__}.', sub='end')
488
507
  self.stop_process()
@@ -532,13 +551,16 @@ class Command(Runner):
532
551
  if self.no_process:
533
552
  return
534
553
 
535
- # Yield command stats (CPU, memory, conns ...)
536
- # TODO: enable stats support with timer
537
- if self.last_updated_stat and (time() - self.last_updated_stat) < CONFIG.runners.stat_update_frequency:
554
+ def process_monitor_queue(self):
555
+ """Process and yield any queued items from monitor thread."""
556
+ if self.monitor_queue is None:
538
557
  return
539
-
540
- yield from self.stats(self.memory_limit_mb)
541
- self.last_updated_stat = time()
558
+ while not self.monitor_queue.empty():
559
+ try:
560
+ monitor_item = self.monitor_queue.get_nowait()
561
+ yield monitor_item
562
+ except queue.Empty:
563
+ break
542
564
 
543
565
  def print_description(self):
544
566
  """Print description"""
@@ -585,6 +607,100 @@ class Command(Runner):
585
607
  if exit_ok:
586
608
  self.exit_ok = True
587
609
 
610
+ def _stop_monitor_thread(self):
611
+ """Stop monitor thread."""
612
+ if self.monitor_thread and self.monitor_thread.is_alive() and self.monitor_stop_event:
613
+ self.monitor_stop_event.set()
614
+ self.monitor_thread.join(timeout=2.0)
615
+
616
+ def _monitor_process(self):
617
+ """Monitor thread that checks process health and kills if necessary."""
618
+ last_stats_time = 0
619
+
620
+ while not self.monitor_stop_event.is_set():
621
+ if not self.process or not self.process.pid:
622
+ break
623
+
624
+ try:
625
+ current_time = time()
626
+ self.debug('Collecting monitor items', sub='monitor')
627
+
628
+ # Collect and queue stats at regular intervals
629
+ if (current_time - last_stats_time) >= CONFIG.runners.stat_update_frequency:
630
+ stats_items = list(self._collect_stats())
631
+ for stat_item in stats_items:
632
+ if self.monitor_queue is not None:
633
+ self.monitor_queue.put(stat_item)
634
+ last_stats_time = current_time
635
+
636
+ # Check memory usage from collected stats
637
+ if self.memory_limit_mb and self.memory_limit_mb != -1:
638
+ total_mem = sum(stat_item.extra_data.get('memory_info', {}).get('rss', 0) / 1024 / 1024 for stat_item in stats_items) # noqa: E501
639
+ if total_mem > self.memory_limit_mb:
640
+ warning = Warning(message=f'Memory limit {self.memory_limit_mb}MB exceeded (actual: {total_mem:.2f}MB)')
641
+ if self.monitor_queue is not None:
642
+ self.monitor_queue.put(warning)
643
+ self.stop_process(exit_ok=True, sig=signal.SIGTERM)
644
+ break
645
+
646
+ # Check execution time
647
+ if self.process_start_time and CONFIG.celery.task_max_timeout != -1:
648
+ elapsed_time = current_time - self.process_start_time
649
+ if elapsed_time > CONFIG.celery.task_max_timeout:
650
+ warning = Warning(message=f'Task timeout {CONFIG.celery.task_max_timeout}s exceeded')
651
+ if self.monitor_queue is not None:
652
+ self.monitor_queue.put(warning)
653
+ self.stop_process(exit_ok=True, sig=signal.SIGTERM)
654
+ break
655
+
656
+ # Check retry count
657
+ # TODO: remove this
658
+ # if CONFIG.celery.task_max_retries and self.retry_count >= CONFIG.celery.task_max_retries:
659
+ # warning = Warning(message=f'Max retries {CONFIG.celery.task_max_retries} exceeded (actual: {self.retry_count})')
660
+ # self.monitor_queue.put(warning)
661
+ # self.stop_process(exit_ok=False, sig=signal.SIGTERM)
662
+ # break
663
+
664
+ except Exception as e:
665
+ self.debug(f'Monitor thread error: {e}', sub='monitor')
666
+ warning = Warning(message=f'Monitor thread error: {e}')
667
+ if self.monitor_queue is not None:
668
+ self.monitor_queue.put(warning)
669
+ break
670
+
671
+ # Sleep for a short interval before next check (stat update frequency)
672
+ self.monitor_stop_event.wait(CONFIG.runners.stat_update_frequency)
673
+
674
+ def _collect_stats(self):
675
+ """Collect stats about the current running process, if any."""
676
+ if not self.process or not self.process.pid:
677
+ return
678
+ proc = psutil.Process(self.process.pid)
679
+ stats = Command.get_process_info(proc, children=True)
680
+ total_mem = 0
681
+ for info in stats:
682
+ name = info['name']
683
+ pid = info['pid']
684
+ cpu_percent = info['cpu_percent']
685
+ # mem_percent = info['memory_percent']
686
+ mem_rss = round(info['memory_info']['rss'] / 1024 / 1024, 2)
687
+ total_mem += mem_rss
688
+ self.debug(f'{name} {pid} {mem_rss}MB', sub='monitor')
689
+ net_conns = info.get('net_connections') or []
690
+ extra_data = {k: v for k, v in info.items() if k not in ['cpu_percent', 'memory_percent', 'net_connections']}
691
+ yield Stat(
692
+ name=name,
693
+ pid=pid,
694
+ cpu=cpu_percent,
695
+ memory=mem_rss,
696
+ memory_limit=self.memory_limit_mb,
697
+ net_conns=len(net_conns),
698
+ extra_data=extra_data
699
+ )
700
+ # self.debug(f'Total mem: {total_mem}MB, memory limit: {self.memory_limit_mb}', sub='monitor')
701
+ # if self.memory_limit_mb and self.memory_limit_mb != -1 and total_mem > self.memory_limit_mb:
702
+ # raise MemoryError(f'Memory limit {self.memory_limit_mb}MB reached for {self.unique_name}')
703
+
588
704
  def stats(self, memory_limit_mb=None):
589
705
  """Gather stats about the current running process, if any."""
590
706
  if not self.process or not self.process.pid:
@@ -599,7 +715,7 @@ class Command(Runner):
599
715
  mem_percent = info['memory_percent']
600
716
  mem_rss = round(info['memory_info']['rss'] / 1024 / 1024, 2)
601
717
  total_mem += mem_rss
602
- self.debug(f'{name} {pid} {mem_rss}MB', sub='stats')
718
+ self.debug(f'process: {name} pid: {pid} memory: {mem_rss}MB', sub='stats')
603
719
  net_conns = info.get('net_connections') or []
604
720
  extra_data = {k: v for k, v in info.items() if k not in ['cpu_percent', 'memory_percent', 'net_connections']}
605
721
  yield Stat(
@@ -701,6 +817,8 @@ class Command(Runner):
701
817
 
702
818
  def _wait_for_end(self):
703
819
  """Wait for process to finish and process output and return code."""
820
+ self._stop_monitor_thread()
821
+ yield from self.process_monitor_queue()
704
822
  if not self.process:
705
823
  return
706
824
  for line in self.process.stdout.readlines():
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes