@rakay-technology/kraft 0.7.4

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 (504) hide show
  1. package/README.md +263 -0
  2. package/dist/bare-HL2GUYE2.js +27 -0
  3. package/dist/bowser-HNOTPGJO.js +2825 -0
  4. package/dist/chunk-3CJLKL6E.js +124 -0
  5. package/dist/chunk-3NQQXMDR.js +2098 -0
  6. package/dist/chunk-42WQABXM.js +439 -0
  7. package/dist/chunk-5GPXMTE4.js +25 -0
  8. package/dist/chunk-AJ4QZLZ2.js +1069 -0
  9. package/dist/chunk-AO252K7Z.js +137 -0
  10. package/dist/chunk-BQMFIXHI.js +975 -0
  11. package/dist/chunk-DM3PKWDC.js +2453 -0
  12. package/dist/chunk-DOD2BXHQ.js +1364 -0
  13. package/dist/chunk-DRK3MEDS.js +2870 -0
  14. package/dist/chunk-EW4PHKB2.js +13914 -0
  15. package/dist/chunk-HMJUFZK7.js +340 -0
  16. package/dist/chunk-I2TOOF2A.js +517 -0
  17. package/dist/chunk-I3DPMTSF.js +29 -0
  18. package/dist/chunk-JSCZT76F.js +2588 -0
  19. package/dist/chunk-MNV2QFHW.js +138 -0
  20. package/dist/chunk-MOLPYPVH.js +2529 -0
  21. package/dist/chunk-N4OALTFK.js +295 -0
  22. package/dist/chunk-OFXBNFNL.js +458 -0
  23. package/dist/chunk-ONKEYSMY.js +1388 -0
  24. package/dist/chunk-PDW4L3NV.js +17 -0
  25. package/dist/chunk-PNNXWA2A.js +1947 -0
  26. package/dist/chunk-PNRPKRJU.js +11105 -0
  27. package/dist/chunk-PVOWHIND.js +26781 -0
  28. package/dist/chunk-PXKCZB2I.js +46 -0
  29. package/dist/chunk-PZVKPCKE.js +475 -0
  30. package/dist/chunk-QWHBRY2K.js +4945 -0
  31. package/dist/chunk-RFOBMHQ7.js +338 -0
  32. package/dist/chunk-RHLNVZMS.js +28 -0
  33. package/dist/chunk-RVER2EOO.js +577 -0
  34. package/dist/chunk-S3QBGGVP.js +81 -0
  35. package/dist/chunk-TAML5WMA.js +198 -0
  36. package/dist/chunk-THTEUDBC.js +3188 -0
  37. package/dist/chunk-WYNM7L22.js +329 -0
  38. package/dist/chunk-XGCG4MD5.js +102 -0
  39. package/dist/chunk-XVKUFMIB.js +148 -0
  40. package/dist/chunk-XZYFZ6A5.js +50 -0
  41. package/dist/chunk-Y7PVJUXJ.js +2275 -0
  42. package/dist/chunk-YRSZPVMB.js +306 -0
  43. package/dist/chunk-YZUKHG3S.js +362 -0
  44. package/dist/chunk-ZVFS4LUA.js +116 -0
  45. package/dist/chunk-ZWLDLTJP.js +1417 -0
  46. package/dist/detect-676SNZK4.js +48 -0
  47. package/dist/dist-es-2YRDXMUU.js +73 -0
  48. package/dist/dist-es-CCVZEFRZ.js +91 -0
  49. package/dist/dist-es-EDUSMF6Q.js +488 -0
  50. package/dist/dist-es-IOH6G4GM.js +380 -0
  51. package/dist/dist-es-KWSMDPIP.js +26 -0
  52. package/dist/dist-es-UYDDOVZ3.js +173 -0
  53. package/dist/dist-es-XUOR4NQN.js +321 -0
  54. package/dist/docker-GOT3Q7DJ.js +41 -0
  55. package/dist/docker-edge-executor-VO6JHHW2.js +19 -0
  56. package/dist/edge-import-NFCHIW7G.js +50 -0
  57. package/dist/edge-preflight-QUJ757C6.js +45 -0
  58. package/dist/ensure-container-edge-Q7IZXHGM.js +38 -0
  59. package/dist/event-streams-TR4GAC53.js +46 -0
  60. package/dist/executor-GKXKN52K.js +42 -0
  61. package/dist/host-channel-preflight-Z6HYSPKO.js +48 -0
  62. package/dist/index.d.ts +2 -0
  63. package/dist/index.js +9469 -0
  64. package/dist/loadSso-UGF4JMRK.js +581 -0
  65. package/dist/local-executor-OMMDKXCM.js +13 -0
  66. package/dist/nginx-VITTWZZU.js +42 -0
  67. package/dist/node-bootstrap.js +277 -0
  68. package/dist/node-entry.js +55 -0
  69. package/dist/noop-CLLJHCPL.js +11 -0
  70. package/dist/openresty-lua-6I3Z6PEQ.js +90 -0
  71. package/dist/privilege-UQAI2HV4.js +20 -0
  72. package/dist/server/engine/ChangeLog +2289 -0
  73. package/dist/server/engine/Documentations +5 -0
  74. package/dist/server/engine/LICENSE +674 -0
  75. package/dist/server/engine/README.md +34 -0
  76. package/dist/server/engine/conf/amavisd +150 -0
  77. package/dist/server/engine/conf/clamav +71 -0
  78. package/dist/server/engine/conf/core +632 -0
  79. package/dist/server/engine/conf/dovecot +151 -0
  80. package/dist/server/engine/conf/fail2ban +72 -0
  81. package/dist/server/engine/conf/global +698 -0
  82. package/dist/server/engine/conf/iredapd +52 -0
  83. package/dist/server/engine/conf/logwatch +28 -0
  84. package/dist/server/engine/conf/postfix +97 -0
  85. package/dist/server/engine/conf/postgresql +93 -0
  86. package/dist/server/engine/conf/spamassassin +39 -0
  87. package/dist/server/engine/dialog/config_via_dialog.sh +258 -0
  88. package/dist/server/engine/dialog/optional_components.sh +122 -0
  89. package/dist/server/engine/dialog/pgsql_config.sh +52 -0
  90. package/dist/server/engine/dialog/virtual_domain_config.sh +94 -0
  91. package/dist/server/engine/functions/amavisd.sh +267 -0
  92. package/dist/server/engine/functions/backend.sh +85 -0
  93. package/dist/server/engine/functions/clamav.sh +100 -0
  94. package/dist/server/engine/functions/cleanup.sh +393 -0
  95. package/dist/server/engine/functions/dovecot.sh +583 -0
  96. package/dist/server/engine/functions/fail2ban.sh +209 -0
  97. package/dist/server/engine/functions/iredapd.sh +356 -0
  98. package/dist/server/engine/functions/optional_components.sh +24 -0
  99. package/dist/server/engine/functions/packages.sh +522 -0
  100. package/dist/server/engine/functions/packages_freebsd.sh +179 -0
  101. package/dist/server/engine/functions/postfix.sh +457 -0
  102. package/dist/server/engine/functions/postgresql.sh +239 -0
  103. package/dist/server/engine/functions/spamassassin.sh +49 -0
  104. package/dist/server/engine/functions/system_accounts.sh +128 -0
  105. package/dist/server/engine/iRedMail.sh +144 -0
  106. package/dist/server/engine/pkgs/get_all.sh +338 -0
  107. package/dist/server/engine/pkgs/pkgs.freebsd.sha256 +3 -0
  108. package/dist/server/engine/pkgs/pkgs.openbsd.sha256 +6 -0
  109. package/dist/server/engine/pkgs/pkgs.sha256 +1 -0
  110. package/dist/server/engine/samples/amavisd/amavisd.conf +702 -0
  111. package/dist/server/engine/samples/amavisd/amavisd.mysql +278 -0
  112. package/dist/server/engine/samples/amavisd/amavisd.pgsql +253 -0
  113. package/dist/server/engine/samples/amavisd/amavisd.schema +942 -0
  114. package/dist/server/engine/samples/amavisd/default_spam_policy.sql +46 -0
  115. package/dist/server/engine/samples/dovecot/dovecot-2.4-mariadb.conf +758 -0
  116. package/dist/server/engine/samples/dovecot/dovecot-2.4-openldap.conf +767 -0
  117. package/dist/server/engine/samples/dovecot/dovecot-2.4-pgsql.conf +762 -0
  118. package/dist/server/engine/samples/dovecot/dovecot-last-login.conf +50 -0
  119. package/dist/server/engine/samples/dovecot/dovecot-ldap.conf +21 -0
  120. package/dist/server/engine/samples/dovecot/dovecot-share-folder.conf +22 -0
  121. package/dist/server/engine/samples/dovecot/dovecot-sql.conf +28 -0
  122. package/dist/server/engine/samples/dovecot/dovecot-used-quota.conf +13 -0
  123. package/dist/server/engine/samples/dovecot/dovecot.conf +484 -0
  124. package/dist/server/engine/samples/dovecot/dovecot.sieve +7 -0
  125. package/dist/server/engine/samples/dovecot/dovecot2-quota-warning.sh +38 -0
  126. package/dist/server/engine/samples/dovecot/sql/imap_share_folder.mysql +16 -0
  127. package/dist/server/engine/samples/dovecot/sql/last_login.mysql +12 -0
  128. package/dist/server/engine/samples/dovecot/sql/used_quota.mysql +26 -0
  129. package/dist/server/engine/samples/dovecot/systemd/override.conf +3 -0
  130. package/dist/server/engine/samples/fail2ban/action.d/banned_db.conf +9 -0
  131. package/dist/server/engine/samples/fail2ban/bin/fail2ban_banned_db +268 -0
  132. package/dist/server/engine/samples/fail2ban/fail2ban.local +17 -0
  133. package/dist/server/engine/samples/fail2ban/filter.d/dovecot.iredmail.conf +5 -0
  134. package/dist/server/engine/samples/fail2ban/filter.d/postfix-pregreet.iredmail.conf +16 -0
  135. package/dist/server/engine/samples/fail2ban/filter.d/postfix.iredmail.conf +18 -0
  136. package/dist/server/engine/samples/fail2ban/filter.d/roundcube.iredmail.conf +9 -0
  137. package/dist/server/engine/samples/fail2ban/jail.d/dovecot.local +8 -0
  138. package/dist/server/engine/samples/fail2ban/jail.d/nginx-http-auth.local +8 -0
  139. package/dist/server/engine/samples/fail2ban/jail.d/postfix-pregreet.local +9 -0
  140. package/dist/server/engine/samples/fail2ban/jail.d/postfix.local +8 -0
  141. package/dist/server/engine/samples/fail2ban/jail.d/roundcube.local +9 -0
  142. package/dist/server/engine/samples/fail2ban/jail.d/sogo.local +8 -0
  143. package/dist/server/engine/samples/fail2ban/jail.d/sshd.local +11 -0
  144. package/dist/server/engine/samples/fail2ban/jail.local +6 -0
  145. package/dist/server/engine/samples/fail2ban/openbsd/rc +27 -0
  146. package/dist/server/engine/samples/fail2ban/sql/fail2ban.mysql +49 -0
  147. package/dist/server/engine/samples/fail2ban/sql/fail2ban.pgsql +48 -0
  148. package/dist/server/engine/samples/firewall/firewalld/services/ssh.xml +6 -0
  149. package/dist/server/engine/samples/firewall/firewalld/zones/iredmail.xml +15 -0
  150. package/dist/server/engine/samples/firewall/iptables/ip6tables.init.debian +362 -0
  151. package/dist/server/engine/samples/firewall/iptables/ip6tables.rules +53 -0
  152. package/dist/server/engine/samples/firewall/iptables/iptables.init.debian +363 -0
  153. package/dist/server/engine/samples/firewall/iptables/iptables.rules +54 -0
  154. package/dist/server/engine/samples/firewall/nftables.conf +58 -0
  155. package/dist/server/engine/samples/freebsd/newsyslog.conf.d/dovecot.conf +3 -0
  156. package/dist/server/engine/samples/freebsd/newsyslog.conf.d/iredapd.conf +1 -0
  157. package/dist/server/engine/samples/freebsd/newsyslog.conf.d/mlmmjadmin.conf +1 -0
  158. package/dist/server/engine/samples/freebsd/newsyslog.conf.d/php-fpm.conf +2 -0
  159. package/dist/server/engine/samples/freebsd/newsyslog.conf.d/slapd.conf +1 -0
  160. package/dist/server/engine/samples/freebsd/syslog.d/dovecot.conf +2 -0
  161. package/dist/server/engine/samples/freebsd/syslog.d/fail2ban.conf +2 -0
  162. package/dist/server/engine/samples/freebsd/syslog.d/iredapd.conf +2 -0
  163. package/dist/server/engine/samples/freebsd/syslog.d/mlmmjadmin.conf +2 -0
  164. package/dist/server/engine/samples/freebsd/syslog.d/php-fpm.conf +2 -0
  165. package/dist/server/engine/samples/freebsd/syslog.d/slapd.conf +2 -0
  166. package/dist/server/engine/samples/iredmail/iredmail.mysql +491 -0
  167. package/dist/server/engine/samples/iredmail/iredmail.pgsql +465 -0
  168. package/dist/server/engine/samples/iredmail/iredmail.schema +456 -0
  169. package/dist/server/engine/samples/iredmail/used_quota_triggers_dovecot_2.3.pgsql +40 -0
  170. package/dist/server/engine/samples/iredmail/used_quota_triggers_dovecot_2.4.pgsql +18 -0
  171. package/dist/server/engine/samples/logrotate/dovecot +13 -0
  172. package/dist/server/engine/samples/logrotate/iredapd +13 -0
  173. package/dist/server/engine/samples/logrotate/mlmmjadmin +13 -0
  174. package/dist/server/engine/samples/logrotate/openldap +19 -0
  175. package/dist/server/engine/samples/logrotate/php-fpm +13 -0
  176. package/dist/server/engine/samples/openbsd/ldapd.conf +58 -0
  177. package/dist/server/engine/samples/openbsd/pf.conf +28 -0
  178. package/dist/server/engine/samples/postfix/command_filter.pcre +3 -0
  179. package/dist/server/engine/samples/postfix/freebsd/mailer.conf +9 -0
  180. package/dist/server/engine/samples/postfix/helo_access.pcre +180 -0
  181. package/dist/server/engine/samples/postfix/ldap/catchall_maps.cf +11 -0
  182. package/dist/server/engine/samples/postfix/ldap/recipient_bcc_maps_domain.cf +11 -0
  183. package/dist/server/engine/samples/postfix/ldap/recipient_bcc_maps_user.cf +11 -0
  184. package/dist/server/engine/samples/postfix/ldap/relay_domains.cf +11 -0
  185. package/dist/server/engine/samples/postfix/ldap/sender_bcc_maps_domain.cf +11 -0
  186. package/dist/server/engine/samples/postfix/ldap/sender_bcc_maps_user.cf +11 -0
  187. package/dist/server/engine/samples/postfix/ldap/sender_dependent_relayhost_maps_domain.cf +11 -0
  188. package/dist/server/engine/samples/postfix/ldap/sender_dependent_relayhost_maps_user.cf +11 -0
  189. package/dist/server/engine/samples/postfix/ldap/sender_login_maps.cf +11 -0
  190. package/dist/server/engine/samples/postfix/ldap/transport_maps_domain.cf +11 -0
  191. package/dist/server/engine/samples/postfix/ldap/transport_maps_user.cf +11 -0
  192. package/dist/server/engine/samples/postfix/ldap/virtual_alias_maps.cf +11 -0
  193. package/dist/server/engine/samples/postfix/ldap/virtual_group_maps.cf +11 -0
  194. package/dist/server/engine/samples/postfix/ldap/virtual_group_members_maps.cf +11 -0
  195. package/dist/server/engine/samples/postfix/ldap/virtual_mailbox_domains.cf +11 -0
  196. package/dist/server/engine/samples/postfix/ldap/virtual_mailbox_maps.cf +12 -0
  197. package/dist/server/engine/samples/postfix/main.cf +282 -0
  198. package/dist/server/engine/samples/postfix/main.cf.amavisd +7 -0
  199. package/dist/server/engine/samples/postfix/main.cf.dovecot +8 -0
  200. package/dist/server/engine/samples/postfix/main.cf.ldap +39 -0
  201. package/dist/server/engine/samples/postfix/main.cf.mlmmj +5 -0
  202. package/dist/server/engine/samples/postfix/main.cf.mysql +39 -0
  203. package/dist/server/engine/samples/postfix/main.cf.pgsql +39 -0
  204. package/dist/server/engine/samples/postfix/main.cf.postscreen +30 -0
  205. package/dist/server/engine/samples/postfix/master.cf +101 -0
  206. package/dist/server/engine/samples/postfix/mysql/catchall_maps.cf +5 -0
  207. package/dist/server/engine/samples/postfix/mysql/domain_alias_catchall_maps.cf +5 -0
  208. package/dist/server/engine/samples/postfix/mysql/domain_alias_maps.cf +5 -0
  209. package/dist/server/engine/samples/postfix/mysql/recipient_bcc_maps_domain.cf +5 -0
  210. package/dist/server/engine/samples/postfix/mysql/recipient_bcc_maps_user.cf +5 -0
  211. package/dist/server/engine/samples/postfix/mysql/relay_domains.cf +5 -0
  212. package/dist/server/engine/samples/postfix/mysql/sender_bcc_maps_domain.cf +5 -0
  213. package/dist/server/engine/samples/postfix/mysql/sender_bcc_maps_user.cf +5 -0
  214. package/dist/server/engine/samples/postfix/mysql/sender_dependent_relayhost_maps.cf +6 -0
  215. package/dist/server/engine/samples/postfix/mysql/sender_login_maps.cf +5 -0
  216. package/dist/server/engine/samples/postfix/mysql/transport_maps_domain.cf +5 -0
  217. package/dist/server/engine/samples/postfix/mysql/transport_maps_maillist.cf +5 -0
  218. package/dist/server/engine/samples/postfix/mysql/transport_maps_user.cf +5 -0
  219. package/dist/server/engine/samples/postfix/mysql/virtual_alias_maps.cf +5 -0
  220. package/dist/server/engine/samples/postfix/mysql/virtual_mailbox_domains.cf +5 -0
  221. package/dist/server/engine/samples/postfix/mysql/virtual_mailbox_maps.cf +5 -0
  222. package/dist/server/engine/samples/postfix/pgsql/catchall_maps.cf +5 -0
  223. package/dist/server/engine/samples/postfix/pgsql/domain_alias_catchall_maps.cf +5 -0
  224. package/dist/server/engine/samples/postfix/pgsql/domain_alias_maps.cf +5 -0
  225. package/dist/server/engine/samples/postfix/pgsql/recipient_bcc_maps_domain.cf +5 -0
  226. package/dist/server/engine/samples/postfix/pgsql/recipient_bcc_maps_user.cf +5 -0
  227. package/dist/server/engine/samples/postfix/pgsql/relay_domains.cf +5 -0
  228. package/dist/server/engine/samples/postfix/pgsql/sender_bcc_maps_domain.cf +5 -0
  229. package/dist/server/engine/samples/postfix/pgsql/sender_bcc_maps_user.cf +5 -0
  230. package/dist/server/engine/samples/postfix/pgsql/sender_dependent_relayhost_maps.cf +6 -0
  231. package/dist/server/engine/samples/postfix/pgsql/sender_login_maps.cf +5 -0
  232. package/dist/server/engine/samples/postfix/pgsql/transport_maps_domain.cf +5 -0
  233. package/dist/server/engine/samples/postfix/pgsql/transport_maps_maillist.cf +5 -0
  234. package/dist/server/engine/samples/postfix/pgsql/transport_maps_user.cf +5 -0
  235. package/dist/server/engine/samples/postfix/pgsql/virtual_alias_maps.cf +5 -0
  236. package/dist/server/engine/samples/postfix/pgsql/virtual_mailbox_domains.cf +5 -0
  237. package/dist/server/engine/samples/postfix/pgsql/virtual_mailbox_maps.cf +5 -0
  238. package/dist/server/engine/samples/postfix/postscreen_access.cidr +6 -0
  239. package/dist/server/engine/samples/postgresql/sql/add_first_domain_and_user.sql +34 -0
  240. package/dist/server/engine/samples/postgresql/sql/grant_permissions.sql +14 -0
  241. package/dist/server/engine/samples/postgresql/sql/init_vmail_db.sql +15 -0
  242. package/dist/server/engine/samples/rsyslog.d/1-iredmail-dovecot.conf +23 -0
  243. package/dist/server/engine/samples/rsyslog.d/1-iredmail-fail2ban.conf +2 -0
  244. package/dist/server/engine/samples/rsyslog.d/1-iredmail-iredapd.conf +12 -0
  245. package/dist/server/engine/samples/rsyslog.d/1-iredmail-mlmmjadmin.conf +12 -0
  246. package/dist/server/engine/samples/rsyslog.d/1-iredmail-openldap.conf +2 -0
  247. package/dist/server/engine/samples/rsyslog.d/1-iredmail-phpfpm.conf +3 -0
  248. package/dist/server/engine/samples/spamassassin/local.cf +186 -0
  249. package/dist/server/engine/samples/spamassassin/razor.conf +1 -0
  250. package/dist/server/engine/samples/systemd/clamd.service.d/override.conf +2 -0
  251. package/dist/server/engine/samples/systemd/mariadb.service.d/override.conf +3 -0
  252. package/dist/server/engine/samples/systemd/slapd.service.d/override.conf +6 -0
  253. package/dist/server/engine/samples/yum/CentOS-Linux-AppStream.repo +6 -0
  254. package/dist/server/engine/samples/yum/CentOS-Linux-PowerTools.repo +6 -0
  255. package/dist/server/engine/samples/yum/CentOS-Stream-AppStream.repo +7 -0
  256. package/dist/server/engine/samples/yum/CentOS-Stream-PowerTools.repo +7 -0
  257. package/dist/server/engine/samples/yum/symas-openldap.repo +6 -0
  258. package/dist/server/engine/tools/add_enabled_service.py +60 -0
  259. package/dist/server/engine/tools/backup_mysql.sh +238 -0
  260. package/dist/server/engine/tools/backup_openldap.sh +230 -0
  261. package/dist/server/engine/tools/backup_pgsql.sh +217 -0
  262. package/dist/server/engine/tools/backup_sogo.sh +156 -0
  263. package/dist/server/engine/tools/create_mail_user_OpenLDAP.py +335 -0
  264. package/dist/server/engine/tools/create_mail_user_OpenLDAP.sh +287 -0
  265. package/dist/server/engine/tools/create_mail_user_SQL.sh +127 -0
  266. package/dist/server/engine/tools/enable_postscreen.sh +144 -0
  267. package/dist/server/engine/tools/fail2ban_unban_ip.sh +39 -0
  268. package/dist/server/engine/tools/find_sasl_login_ip.sh +20 -0
  269. package/dist/server/engine/tools/find_top_sasl_usernames.sh +28 -0
  270. package/dist/server/engine/tools/generate_ssl_keys.sh +85 -0
  271. package/dist/server/engine/tools/kill_high_cpu_sogo_process.sh +32 -0
  272. package/dist/server/engine/tools/ldap_assign_user_to_groups.py +151 -0
  273. package/dist/server/engine/tools/ldap_move_members_to_another_group.py +129 -0
  274. package/dist/server/engine/tools/migrate_sql_alias_table.py +177 -0
  275. package/dist/server/engine/update/0.9.8/amavisd.mysql +29 -0
  276. package/dist/server/engine/update/0.9.8/amavisd.pgsql +39 -0
  277. package/dist/server/engine/update/0.9.8/iredmail.mysql +53 -0
  278. package/dist/server/engine/update/0.9.8/iredmail.pgsql +61 -0
  279. package/dist/server/engine/update/0.9.9/iredmail.mysql +2 -0
  280. package/dist/server/engine/update/0.9.9/iredmail.pgsql +2 -0
  281. package/dist/server/engine/update/1.0/iredmail.mysql +2 -0
  282. package/dist/server/engine/update/1.0/iredmail.pgsql +2 -0
  283. package/dist/server/engine/update/1.2/last_login.mysql +16 -0
  284. package/dist/server/engine/update/1.4.0/iredmail.mysql +22 -0
  285. package/dist/server/engine/update/1.4.0/iredmail.pgsql +20 -0
  286. package/dist/server/engine/update/1.4.1/iredmail.mysql +3 -0
  287. package/dist/server/engine/update/1.4.1/iredmail.pgsql +3 -0
  288. package/dist/server/engine/update/1.4.1/sogo.mysql +11 -0
  289. package/dist/server/engine/update/1.4.1/sogo.pgsql +30 -0
  290. package/dist/server/engine/update/1.4.1/update-ldap.py +66 -0
  291. package/dist/server/engine/update/1.4.2/iredmail.mysql +9 -0
  292. package/dist/server/engine/update/1.4.2/iredmail.pgsql +9 -0
  293. package/dist/server/engine/update/1.4.2/sogo.pgsql +30 -0
  294. package/dist/server/engine/update/1.6.0/sogo_view.pgsql +27 -0
  295. package/dist/server/engine/update/1.6.3/iredmail.mysql +1 -0
  296. package/dist/server/engine/update/1.7.0/fail2ban.mysql +11 -0
  297. package/dist/server/engine/update/1.7.0/fail2ban.pgsql +12 -0
  298. package/dist/server/engine/update/1.7.1/amavisd.mysql +1 -0
  299. package/dist/server/engine/update/1.7.2/vmail.mysql +30 -0
  300. package/dist/server/engine/update/1.7.3/deleted_mailboxes.mysql +43 -0
  301. package/dist/server/engine/update/1.7.3/iredadmin.mysql +26 -0
  302. package/dist/server/engine/update/1.7.3/vmail.mysql +79 -0
  303. package/dist/server/engine/update/1.7.3/vmail.pgsql +9 -0
  304. package/dist/server/engine/update/README.md +1 -0
  305. package/dist/server/engine/update/ldap/README.md +39 -0
  306. package/dist/server/engine/update/ldap/update-ldap-dovecot-2.3.py +69 -0
  307. package/dist/server/index.js +349016 -0
  308. package/dist/server/lua/geo_country.lua +127 -0
  309. package/dist/server/lua/maxminddb.lua +403 -0
  310. package/dist/server/lua/mgmt_api.lua +654 -0
  311. package/dist/server/lua/pipe_stream.lua +103 -0
  312. package/dist/server/lua/rules_guard.lua +132 -0
  313. package/dist/server/lua/rules_lib.lua +176 -0
  314. package/dist/server/lua/site_logger.lua +405 -0
  315. package/dist/server/lua/webhook_handler.lua +126 -0
  316. package/dist/server/migrations/0000_init.sql +666 -0
  317. package/dist/server/migrations/0001_brief_tomorrow_man.sql +47 -0
  318. package/dist/server/migrations/0002_funny_dazzler.sql +2 -0
  319. package/dist/server/migrations/0003_third_killer_shrike.sql +1 -0
  320. package/dist/server/migrations/0004_session_active_org_not_null.sql +10 -0
  321. package/dist/server/migrations/0005_warm_amazoness.sql +3 -0
  322. package/dist/server/migrations/0006_lumpy_joystick.sql +1 -0
  323. package/dist/server/migrations/0007_outgoing_maginty.sql +2 -0
  324. package/dist/server/migrations/0008_frozen_cutover.sql +3 -0
  325. package/dist/server/migrations/0009_credits_billing.sql +142 -0
  326. package/dist/server/migrations/0010_credit_grant_dedupe.sql +14 -0
  327. package/dist/server/migrations/0011_remove_credit_ledger.sql +23 -0
  328. package/dist/server/migrations/0012_smart_deploys_and_git_rollback.sql +81 -0
  329. package/dist/server/migrations/0013_fix_check_run_rollup_unique.sql +38 -0
  330. package/dist/server/migrations/0014_default_rollback_git.sql +9 -0
  331. package/dist/server/migrations/0015_billing_idempotency_grants.sql +41 -0
  332. package/dist/server/migrations/0016_github_auth_high_findings.sql +30 -0
  333. package/dist/server/migrations/0017_spooky_rumiko_fujikawa.sql +6 -0
  334. package/dist/server/migrations/0018_calm_shatterstar.sql +1 -0
  335. package/dist/server/migrations/0019_rename_workspace_install_to_prepare.sql +1 -0
  336. package/dist/server/migrations/0020_uneven_the_renegades.sql +13 -0
  337. package/dist/server/migrations/0021_shiny_firestar.sql +2 -0
  338. package/dist/server/migrations/0022_version_on_success_backfill.sql +6 -0
  339. package/dist/server/migrations/0023_productive_gorilla_man.sql +17 -0
  340. package/dist/server/migrations/0024_sleepy_peter_quill.sql +2 -0
  341. package/dist/server/migrations/0025_smooth_blue_shield.sql +1 -0
  342. package/dist/server/migrations/0026_mail_backup_source.sql +11 -0
  343. package/dist/server/migrations/0027_opposite_anthem.sql +6 -0
  344. package/dist/server/migrations/0028_stale_rogue.sql +18 -0
  345. package/dist/server/migrations/0029_bouncy_loners.sql +16 -0
  346. package/dist/server/migrations/0030_bright_darkstar.sql +6 -0
  347. package/dist/server/migrations/0031_conscious_catseye.sql +13 -0
  348. package/dist/server/migrations/0032_wet_mimic.sql +51 -0
  349. package/dist/server/migrations/0033_eminent_molly_hayes.sql +2 -0
  350. package/dist/server/migrations/0034_nervous_surge.sql +1 -0
  351. package/dist/server/migrations/0035_thankful_morgan_stark.sql +1 -0
  352. package/dist/server/migrations/0036_gorgeous_scalphunter.sql +17 -0
  353. package/dist/server/migrations/0037_clever_makkari.sql +28 -0
  354. package/dist/server/migrations/0038_young_magneto.sql +1 -0
  355. package/dist/server/migrations/0039_secret_lady_ursula.sql +15 -0
  356. package/dist/server/migrations/0040_sharp_red_wolf.sql +16 -0
  357. package/dist/server/migrations/0041_round_spyke.sql +4 -0
  358. package/dist/server/migrations/0042_dapper_havok.sql +1 -0
  359. package/dist/server/migrations/0043_fast_sharon_ventura.sql +15 -0
  360. package/dist/server/migrations/0044_amused_shape.sql +8 -0
  361. package/dist/server/migrations/0045_amusing_korath.sql +1 -0
  362. package/dist/server/migrations/0046_white_harrier.sql +35 -0
  363. package/dist/server/migrations/0047_sloppy_strong_guy.sql +2 -0
  364. package/dist/server/migrations/0048_funny_emma_frost.sql +2 -0
  365. package/dist/server/migrations/0049_nappy_piledriver.sql +3 -0
  366. package/dist/server/migrations/0050_add_service_deployment_image_digest.sql +1 -0
  367. package/dist/server/migrations/0051_add_update_status.sql +19 -0
  368. package/dist/server/migrations/0052_add_notice_target.sql +2 -0
  369. package/dist/server/migrations/0053_colossal_sandman.sql +14 -0
  370. package/dist/server/migrations/0054_add_instance_smtp.sql +5 -0
  371. package/dist/server/migrations/0055_rainy_klaw.sql +22 -0
  372. package/dist/server/migrations/0056_moaning_daredevil.sql +2 -0
  373. package/dist/server/migrations/0057_complete_prism.sql +18 -0
  374. package/dist/server/migrations/0058_add_server_is_local.sql +1 -0
  375. package/dist/server/migrations/0059_legal_fixer.sql +18 -0
  376. package/dist/server/migrations/0060_add_route_strategy_host_port.sql +3 -0
  377. package/dist/server/migrations/0061_notification_default_multi_kind.sql +3 -0
  378. package/dist/server/migrations/0062_add_incoming_webhook.sql +21 -0
  379. package/dist/server/migrations/0063_add_migration_logs.sql +1 -0
  380. package/dist/server/migrations/0064_add_migration_partial.sql +2 -0
  381. package/dist/server/migrations/0065_custom_app_template.sql +14 -0
  382. package/dist/server/migrations/0066_ensure_migration_partial_columns.sql +2 -0
  383. package/dist/server/migrations/0067_webhook_delivery.sql +28 -0
  384. package/dist/server/migrations/0068_add_migration_target_volumes.sql +1 -0
  385. package/dist/server/migrations/0069_add_project_composite_routes.sql +1 -0
  386. package/dist/server/migrations/0070_forward_git_to_server.sql +1 -0
  387. package/dist/server/migrations/0071_gh_device_token.sql +3 -0
  388. package/dist/server/migrations/0072_gh_device_token_method.sql +1 -0
  389. package/dist/server/migrations/0073_project_volumes_object_storage.sql +3 -0
  390. package/dist/server/migrations/0074_project_compose_path.sql +1 -0
  391. package/dist/server/migrations/0075_service_command_argv.sql +1 -0
  392. package/dist/server/migrations/0076_rollback_retention.sql +17 -0
  393. package/dist/server/migrations/0077_domain_redirect.sql +13 -0
  394. package/dist/server/migrations/0078_grant_source_scope.sql +30 -0
  395. package/dist/server/migrations/0079_project_readiness.sql +18 -0
  396. package/dist/server/migrations/0080_deployment_error_classification.sql +26 -0
  397. package/dist/server/migrations/0081_project_readiness_repair.sql +34 -0
  398. package/dist/server/migrations/0082_edge_target_verification.sql +39 -0
  399. package/dist/server/migrations/0083_service_incident.sql +84 -0
  400. package/dist/server/migrations/0084_analytics_daily_rollup.sql +72 -0
  401. package/dist/server/migrations/0085_resource_usage.sql +68 -0
  402. package/dist/server/migrations/0086_remote_infra_updates.sql +41 -0
  403. package/dist/server/migrations/0087_server_container_version_columns.sql +17 -0
  404. package/dist/server/migrations/0088_audit_source_and_settings.sql +27 -0
  405. package/dist/server/migrations/0089_auto_scan_infra.sql +7 -0
  406. package/dist/server/migrations/0090_project_collect_paths.sql +18 -0
  407. package/dist/server/migrations/0091_project_server_id.sql +26 -0
  408. package/dist/server/migrations/0092_project_internal_alias.sql +13 -0
  409. package/dist/server/migrations/0093_service_incident_org_index.sql +13 -0
  410. package/dist/server/migrations/0094_backup_restore_meta.sql +15 -0
  411. package/dist/server/migrations/0095_backup_restore_cancel.sql +19 -0
  412. package/dist/server/migrations/0096_backup_policy_retention_defaults.sql +20 -0
  413. package/dist/server/migrations/0097_update_status_upstream_only.sql +35 -0
  414. package/dist/server/migrations/0098_instance_product_mode.sql +12 -0
  415. package/dist/server/migrations/0099_mail_webmail_project.sql +24 -0
  416. package/dist/server/migrations/0100_server_ssh_private_key.sql +11 -0
  417. package/dist/server/migrations/0101_mail_resume_step.sql +12 -0
  418. package/dist/server/migrations/0102_project_workload_axes.sql +19 -0
  419. package/dist/server/migrations/0103_dns_credential.sql +41 -0
  420. package/dist/server/migrations/0104_instance_host_control.sql +15 -0
  421. package/dist/server/migrations/0105_mail_inbound_rules.sql +80 -0
  422. package/dist/server/migrations/0106_mcp_call_tracking.sql +23 -0
  423. package/dist/server/migrations/0107_build_minute_metering_indexes.sql +20 -0
  424. package/dist/server/migrations/0108_credential.sql +68 -0
  425. package/dist/server/migrations/0109_service_build_args.sql +9 -0
  426. package/dist/server/migrations/0110_service_deployment_host_ports.sql +18 -0
  427. package/dist/server/migrations/0111_host_port_claim.sql +381 -0
  428. package/dist/server/migrations/0112_backup_run_execution_lease.sql +12 -0
  429. package/dist/server/migrations/0113_docker_migration_execution_lease.sql +11 -0
  430. package/dist/server/migrations/0114_build_session_execution_lease_index.sql +6 -0
  431. package/dist/server/migrations/0115_orphan_target_key.sql +3 -0
  432. package/dist/server/migrations/0116_domain_ssl_challenge.sql +2 -0
  433. package/dist/server/migrations/0117_github_installation_workspace_scope.sql +24 -0
  434. package/dist/server/migrations/0118_git_sources.sql +66 -0
  435. package/dist/server/migrations/0119_backup_run_batch.sql +8 -0
  436. package/dist/server/migrations/0120_domain_primary_unique.sql +24 -0
  437. package/dist/server/migrations/0121_host_port_claim_overlap.sql +14 -0
  438. package/dist/server/migrations/0122_data_transfer_staging.sql +45 -0
  439. package/dist/server/migrations/0123_drop_cloud_infra.sql +7 -0
  440. package/dist/server/migrations/0124_mail_dmarc_report.sql +23 -0
  441. package/dist/server/migrations/0125_server_security_snapshot.sql +22 -0
  442. package/dist/server/migrations/meta/0000_snapshot.json +4848 -0
  443. package/dist/server/migrations/meta/0001_snapshot.json +4991 -0
  444. package/dist/server/migrations/meta/0002_snapshot.json +5013 -0
  445. package/dist/server/migrations/meta/0003_snapshot.json +5020 -0
  446. package/dist/server/migrations/meta/0004_snapshot.json +5020 -0
  447. package/dist/server/migrations/meta/0005_snapshot.json +5039 -0
  448. package/dist/server/migrations/meta/0006_snapshot.json +5045 -0
  449. package/dist/server/migrations/meta/0007_snapshot.json +5057 -0
  450. package/dist/server/migrations/meta/0016_snapshot.json +6053 -0
  451. package/dist/server/migrations/meta/0017_snapshot.json +6091 -0
  452. package/dist/server/migrations/meta/0018_snapshot.json +6098 -0
  453. package/dist/server/migrations/meta/0019_snapshot.json +6098 -0
  454. package/dist/server/migrations/meta/0020_snapshot.json +6201 -0
  455. package/dist/server/migrations/meta/0021_snapshot.json +6213 -0
  456. package/dist/server/migrations/meta/0022_snapshot.json +6213 -0
  457. package/dist/server/migrations/meta/0023_snapshot.json +6331 -0
  458. package/dist/server/migrations/meta/0024_snapshot.json +6343 -0
  459. package/dist/server/migrations/meta/0025_snapshot.json +6350 -0
  460. package/dist/server/migrations/meta/0026_snapshot.json +6440 -0
  461. package/dist/server/migrations/meta/0027_snapshot.json +6447 -0
  462. package/dist/server/migrations/meta/0028_snapshot.json +6581 -0
  463. package/dist/server/migrations/meta/0029_snapshot.json +6710 -0
  464. package/dist/server/migrations/meta/0030_snapshot.json +6748 -0
  465. package/dist/server/migrations/meta/0031_snapshot.json +6859 -0
  466. package/dist/server/migrations/meta/0032_snapshot.json +7222 -0
  467. package/dist/server/migrations/meta/0033_snapshot.json +7249 -0
  468. package/dist/server/migrations/meta/0034_snapshot.json +7255 -0
  469. package/dist/server/migrations/meta/0035_snapshot.json +7262 -0
  470. package/dist/server/migrations/meta/0036_snapshot.json +7392 -0
  471. package/dist/server/migrations/meta/0037_snapshot.json +7602 -0
  472. package/dist/server/migrations/meta/0038_snapshot.json +7609 -0
  473. package/dist/server/migrations/meta/0039_snapshot.json +7714 -0
  474. package/dist/server/migrations/meta/0040_snapshot.json +7820 -0
  475. package/dist/server/migrations/meta/0041_snapshot.json +7833 -0
  476. package/dist/server/migrations/meta/0042_snapshot.json +7839 -0
  477. package/dist/server/migrations/meta/0043_snapshot.json +7938 -0
  478. package/dist/server/migrations/meta/0044_snapshot.json +7982 -0
  479. package/dist/server/migrations/meta/0045_snapshot.json +7989 -0
  480. package/dist/server/migrations/meta/0046_snapshot.json +8263 -0
  481. package/dist/server/migrations/meta/0047_snapshot.json +8275 -0
  482. package/dist/server/migrations/meta/0048_snapshot.json +8289 -0
  483. package/dist/server/migrations/meta/0049_snapshot.json +8308 -0
  484. package/dist/server/migrations/meta/0050_snapshot.json +8314 -0
  485. package/dist/server/migrations/meta/0051_snapshot.json +8460 -0
  486. package/dist/server/migrations/meta/0052_snapshot.json +8472 -0
  487. package/dist/server/migrations/meta/0053_snapshot.json +8556 -0
  488. package/dist/server/migrations/meta/0054_snapshot.json +8586 -0
  489. package/dist/server/migrations/meta/0055_snapshot.json +8756 -0
  490. package/dist/server/migrations/meta/0056_snapshot.json +8777 -0
  491. package/dist/server/migrations/meta/0057_snapshot.json +8904 -0
  492. package/dist/server/migrations/meta/0058_snapshot.json +8911 -0
  493. package/dist/server/migrations/meta/0059_snapshot.json +9061 -0
  494. package/dist/server/migrations/meta/0060_snapshot.json +9081 -0
  495. package/dist/server/migrations/meta/_journal.json +888 -0
  496. package/dist/server/pglite/pglite.data +0 -0
  497. package/dist/server/pglite/pglite.wasm +0 -0
  498. package/dist/setup-JWKZIECM.js +25 -0
  499. package/dist/signin-3M3UPB6K.js +667 -0
  500. package/dist/ssh-client-NWQVRCV7.js +29 -0
  501. package/dist/sso-oidc-3VDIYOEJ.js +834 -0
  502. package/dist/sts-B3WIYLXR.js +1426 -0
  503. package/dist/up-P5DMQ5YO.js +55 -0
  504. package/package.json +58 -0
@@ -0,0 +1,654 @@
1
+ -- mgmt_api.lua
2
+ -- REST analytics endpoints backed by ngx.shared.dict.
3
+ -- Internal management port only (127.0.0.1:9145).
4
+
5
+ local cjson = require "cjson.safe"
6
+
7
+ local analytics = ngx.shared.analytics
8
+ local request_data = ngx.shared.request_data
9
+ local uri = ngx.var.uri
10
+
11
+ local function json(data, code)
12
+ ngx.status = code or 200
13
+ ngx.header["Content-Type"] = "application/json"
14
+ ngx.say(cjson.encode(data))
15
+ return ngx.exit(ngx.status)
16
+ end
17
+
18
+ local function bad(msg)
19
+ return json({ error = msg }, 400)
20
+ end
21
+
22
+ -- ── GET /health ──────────────────────────────────────────────────────────────
23
+ -- Bare "ok", NOT JSON. probeMgmt() in apps/api/src/lib/project-analytics.ts
24
+ -- asserts `body.trim() === "ok"`, so anything richer here silently reports every
25
+ -- edge as unreachable and stops the analytics scrape. Richer diagnostics go on
26
+ -- /status below.
27
+
28
+ if uri == "/health" then
29
+ ngx.say("ok")
30
+ return ngx.exit(200)
31
+ end
32
+
33
+ -- ── GET /status - diagnosable edge health ────────────────────────────────────
34
+ -- Why this exists: geo failing is INVISIBLE otherwise. geo_country.lua pcalls its
35
+ -- FFI binding and returns nil on failure, so a missing libmaxminddb.so or a
36
+ -- missing database presents exactly like "nobody visited from a known country" —
37
+ -- an empty map with no error anywhere. This reports which it is.
38
+ --
39
+ -- Also reports shared-dict headroom: these zones LRU-EVICT when full rather than
40
+ -- erroring, so a full `visitors` zone silently UNDERCOUNTS. Callers surface that
41
+ -- as "approximate" instead of presenting an eviction artifact as a measurement.
42
+
43
+ if uri == "/status" then
44
+ local geo_available, geo_reason, geo_db = false, "geo_country module not loadable", nil
45
+ local geo_ok, geo = pcall(require, "kraft.geo_country")
46
+ if geo_ok and geo and geo.status then
47
+ geo_available, geo_reason, geo_db = geo.status()
48
+ end
49
+
50
+ local dicts = {}
51
+ for _, name in ipairs({ "analytics", "request_data", "rules", "rl_counters", "visitors" }) do
52
+ local d = ngx.shared[name]
53
+ if d then
54
+ dicts[name] = {
55
+ capacity = d.capacity and d:capacity() or nil,
56
+ free_space = d.free_space and d:free_space() or nil,
57
+ }
58
+ end
59
+ end
60
+
61
+ return json({
62
+ ok = true,
63
+ geo = { available = geo_available, reason = geo_reason, db = geo_db },
64
+ dicts = dicts,
65
+ })
66
+ end
67
+
68
+ -- ── /rules - per-route rules cache (written by the API, read by rules_guard) ──
69
+ -- POST /rules body: { "host": "...", "rules": [ { "pathPrefix": "/", "spec": {...} } ] }
70
+ -- → replaces the ruleset for that host in the `rules` shared dict (reload-free).
71
+ -- An empty/absent `rules` array clears the host.
72
+ -- GET /rules?host=... → the stored ruleset (debug).
73
+ -- DELETE /rules?host=... → clear the host's ruleset.
74
+ -- Handled BEFORE the analytics guard: rules use their own dict.
75
+ -- ── /analytics/config - per-host collection switches ─────────────────────────
76
+ --
77
+ -- POST body: { "hosts": [ { "host": "...", "paths": true|false }, ... ] }
78
+ -- GET ?host=... → { host, paths }
79
+ --
80
+ -- A LIST, not a single host: a shared dict is RAM, so a full nginx restart (a reboot, a
81
+ -- `docker restart`, a package upgrade) empties it and every host on the box has to be
82
+ -- re-set at once. `openresty -s reload` preserves the zone — verified — so only a real
83
+ -- restart loses it, which is why the reconcile below exists and why it needs to cost one
84
+ -- round trip per server rather than one per domain.
85
+ --
86
+ -- Lives in the `rules` zone rather than `analytics` for the reason site_logger states:
87
+ -- the analytics zone is a 256 MB churn of counters under LRU pressure, and an evicted
88
+ -- config flag would silently change what is collected. This zone holds a few small values.
89
+ --
90
+ -- Deliberately its own endpoint rather than a field on /rules: the API only pushes /rules
91
+ -- for hosts that HAVE rules, so a project with none would never receive this flag.
92
+ --
93
+ -- No TTL — a shared dict is RAM and is lost on restart either way, so the API re-pushes on
94
+ -- every route apply and the database stays the source of truth.
95
+ if uri == "/analytics/config" then
96
+ local rules = ngx.shared.rules
97
+ if not rules then return json({ error = "rules dict unavailable" }, 503) end
98
+ local method = ngx.req.get_method()
99
+
100
+ if method == "GET" then
101
+ local host = ngx.var.arg_host
102
+ if not host or host == "" then return bad("missing ?host=") end
103
+ host = host:lower()
104
+ return json({ host = host, paths = rules:get("cfg:" .. host .. ":paths") == 1 })
105
+ end
106
+
107
+ if method == "POST" then
108
+ ngx.req.read_body()
109
+ local body = ngx.req.get_body_data()
110
+ if not body then return bad("empty body") end
111
+ local payload = cjson.decode(body)
112
+ if type(payload) ~= "table" or type(payload.hosts) ~= "table" then
113
+ return bad("expected { hosts: [ { host, paths } ] }")
114
+ end
115
+ local applied = 0
116
+ for _, entry in ipairs(payload.hosts) do
117
+ if type(entry) == "table" and entry.host then
118
+ local key = "cfg:" .. tostring(entry.host):lower() .. ":paths"
119
+ if entry.paths == true then
120
+ rules:set(key, 1)
121
+ else
122
+ -- Deleted rather than set to 0, so "absent" is the only off state there
123
+ -- is. Two representations of off is how a reader ends up treating one of
124
+ -- them as on — and absent is also what a restarted box starts from, so
125
+ -- off has to mean exactly the same thing in both cases.
126
+ rules:delete(key)
127
+ end
128
+ applied = applied + 1
129
+ end
130
+ end
131
+ return json({ ok = true, applied = applied })
132
+ end
133
+
134
+ return json({ error = "method not allowed" }, 405)
135
+ end
136
+
137
+ if uri == "/rules" then
138
+ local rules = ngx.shared.rules
139
+ if not rules then return json({ error = "rules dict unavailable" }, 503) end
140
+ local method = ngx.req.get_method()
141
+
142
+ if method == "GET" then
143
+ local host = ngx.var.arg_host
144
+ if not host or host == "" then return bad("missing ?host=") end
145
+ host = host:lower()
146
+ local raw = rules:get(host)
147
+ return json({ host = host, rules = raw and cjson.decode(raw) or {} })
148
+ end
149
+
150
+ if method == "DELETE" then
151
+ local host = ngx.var.arg_host
152
+ if not host or host == "" then return bad("missing ?host=") end
153
+ rules:delete(host:lower())
154
+ return json({ ok = true })
155
+ end
156
+
157
+ if method == "POST" then
158
+ ngx.req.read_body()
159
+ local body = ngx.req.get_body_data()
160
+ if not body then return bad("empty body") end
161
+ local payload = cjson.decode(body)
162
+ if type(payload) ~= "table" or not payload.host then
163
+ return bad("expected { host, rules: [...] }")
164
+ end
165
+ local host = tostring(payload.host):lower()
166
+ local list = payload.rules
167
+ if type(list) ~= "table" or #list == 0 then
168
+ rules:delete(host) -- empty set = clear the host
169
+ return json({ ok = true, host = host, count = 0 })
170
+ end
171
+ local ok, err = rules:set(host, cjson.encode(list))
172
+ if not ok then return json({ error = "set failed: " .. (err or "?") }, 500) end
173
+ return json({ ok = true, host = host, count = #list })
174
+ end
175
+
176
+ return json({ error = "method not allowed" }, 405)
177
+ end
178
+
179
+ if not analytics then
180
+ return json({ error = "analytics dict unavailable" }, 503)
181
+ end
182
+
183
+ -- ── Per-minute country index ─────────────────────────────────────────────────
184
+ --
185
+ -- site_logger writes one key per (domain, minute, country): `c:{domain}:{min}:{CC}`.
186
+ -- Reading them back needs key DISCOVERY, and `get_keys` is the only mechanism.
187
+ --
188
+ -- This used to sit INSIDE the per-minute loop, so a 24h window ran up to 1440
189
+ -- `get_keys(10000)` calls — each a full scan of a 256 MB zone, each holding the
190
+ -- dict lock against every worker. One scan per request instead, bucketed by
191
+ -- minute in a single pass; the loop then does plain table lookups.
192
+ --
193
+ -- `get_keys(0)` (all keys) rather than 10000: the cap silently TRUNCATED, so a
194
+ -- busy box just lost countries with no error. Unbounded is safe precisely because
195
+ -- this is now called once — see the comment on the call.
196
+ --- Read an enumerable set written by site_logger's index_add.
197
+ --
198
+ -- Replaces `get_keys`, which walked EVERY key in the 256 MB zone holding its lock — cost
199
+ -- proportional to zone population rather than to the answer, on a path called four
200
+ -- different ways. Here the counter says how many members exist and the slots name them, so
201
+ -- a read costs exactly as much as the data it returns.
202
+ --
203
+ -- Over-reads slightly by design: a slot can be written twice if two workers race on a
204
+ -- member's first sighting, and slots outlive nothing (same TTL as their counters). Both
205
+ -- collapse harmlessly because the caller keys results by member.
206
+ local function read_index(counter_key, slot_prefix)
207
+ local n = tonumber(analytics:get(counter_key)) or 0
208
+ if n <= 0 then return {} end
209
+ local out, seen = {}, {}
210
+ for i = 1, n do
211
+ local m = analytics:get(slot_prefix .. i)
212
+ if m and not seen[m] then
213
+ seen[m] = true
214
+ out[#out + 1] = m
215
+ end
216
+ end
217
+ return out
218
+ end
219
+
220
+ --- Drain a counter WITHOUT losing a concurrent increment.
221
+ --
222
+ -- Was `get` then `delete`: a request landing between the two had its count deleted along
223
+ -- with the value that had just been read. Subtracting exactly what was read leaves any
224
+ -- concurrent increment behind as a residual the next scrape collects.
225
+ local function drain(key)
226
+ local v = tonumber(analytics:get(key)) or 0
227
+ if v ~= 0 then analytics:incr(key, -v) end
228
+ return v
229
+ end
230
+
231
+ --- One day's rollup for one domain, read through its indexes.
232
+ --
233
+ -- Returns the same `suffix → value` shape the old whole-zone classifier produced, so both
234
+ -- callers stayed unchanged: bare country codes, `s:{code}`, `p:{path}`, `v`, `pn`.
235
+ local function read_rollup(domain, day)
236
+ local gpfx = "g:" .. domain .. ":" .. day .. ":"
237
+ local out = {}
238
+
239
+ for _, cc in ipairs(read_index(gpfx .. "cn", gpfx .. "ci:")) do
240
+ out[cc] = tonumber(analytics:get(gpfx .. cc)) or 0
241
+ end
242
+ for _, code in ipairs(read_index(gpfx .. "sn", gpfx .. "si:")) do
243
+ out["s:" .. code] = tonumber(analytics:get(gpfx .. "s:" .. code)) or 0
244
+ end
245
+ -- `pn` doubles as the path index's counter, so paths need no separate one. When path
246
+ -- collection is switched off for this host there is no counter and this reads nothing.
247
+ for _, path in ipairs(read_index(gpfx .. "pn", gpfx .. "pi:")) do
248
+ out["p:" .. path] = tonumber(analytics:get(gpfx .. "p:" .. path)) or 0
249
+ end
250
+ -- The over-cap bucket has a fixed name, so it is not in the index.
251
+ local other = tonumber(analytics:get(gpfx .. "p:other"))
252
+ if other then out["p:other"] = other end
253
+
254
+ local v = tonumber(analytics:get(gpfx .. "v"))
255
+ if v then out["v"] = v end
256
+ return out
257
+ end
258
+
259
+ local function scan_country_index(domain, from_m, to_m)
260
+ -- Walks the requested MINUTES and reads each one's own index. Bounded by the window
261
+ -- (capped at 1440 by the caller), not by how much is in the zone.
262
+ local by_minute = {}
263
+ for m = from_m, to_m do
264
+ local mpfx = "c:" .. domain .. ":" .. m .. ":"
265
+ local ccs = read_index(mpfx .. "n", mpfx .. "i:")
266
+ if #ccs > 0 then
267
+ local slot = {}
268
+ for i = 1, #ccs do
269
+ -- Full key, because the flush drains by key.
270
+ slot[ccs[i]] = mpfx .. ccs[i]
271
+ end
272
+ by_minute[m] = slot
273
+ end
274
+ end
275
+ return by_minute
276
+ end
277
+
278
+ --- Split a namespaced analytics key into kind / domain / mid / rest.
279
+ --
280
+ -- Layouts: `c:{domain}:{minute}:{CC}` and `g:{domain}:{day}:{suffix}`.
281
+ -- Positional, NOT a full split on ":", because `suffix` legitimately contains
282
+ -- colons — a normalized path key looks like `g:x.com:20260803:p:/users/:id`.
283
+ -- Hostnames cannot contain ":", so the first three separators are unambiguous.
284
+ local function split_key(k)
285
+ local p1 = k:find(":", 1, true)
286
+ if not p1 then return nil end
287
+ local p2 = k:find(":", p1 + 1, true)
288
+ if not p2 then return nil end
289
+ local p3 = k:find(":", p2 + 1, true)
290
+ if not p3 then
291
+ return k:sub(1, p1 - 1), k:sub(p1 + 1, p2 - 1), k:sub(p2 + 1), nil
292
+ end
293
+ return k:sub(1, p1 - 1), k:sub(p1 + 1, p2 - 1), k:sub(p2 + 1, p3 - 1), k:sub(p3 + 1)
294
+ end
295
+
296
+ --- Read one minute's country counts out of a scanned index.
297
+ -- @param slot table|nil CC → full dict key, from scan_country_index
298
+ -- @param del boolean drain each key after reading (flush semantics)
299
+ -- @return table|nil counts, or nil when the minute had no country data
300
+ local function read_minute_countries(slot, del)
301
+ if not slot then return nil end
302
+ local counts, any = {}, false
303
+ for cc, key in pairs(slot) do
304
+ -- `drain` subtracts what it read instead of deleting, so a request arriving
305
+ -- mid-flush is not thrown away with the value that was just read.
306
+ counts[cc] = del and drain(key) or (tonumber(analytics:get(key)) or 0)
307
+ any = true
308
+ end
309
+ return any and counts or nil
310
+ end
311
+
312
+ -- ── POST /analytics/collect - the SCRAPER's endpoint: every domain, ONE scan ──
313
+ --
314
+ -- Body: { "day": "YYYYMMDD", "domains": [ { "domain": "x.com", "from": M, "to": M } ] }
315
+ -- Returns: { "domains": { "x.com": { buckets: [...], flushed: N, rollup: {...} } } }
316
+ --
317
+ -- Why this exists rather than looping the single-domain endpoints:
318
+ --
319
+ -- `get_keys` is the only key-discovery mechanism a shared dict offers, it walks the
320
+ -- WHOLE zone, and it holds the dict lock against every nginx worker while it does.
321
+ -- The scraper needs both the per-minute countries (flush) and the daily rollup for
322
+ -- each domain, so calling the per-domain endpoints cost 2N+1 unbounded scans of a
323
+ -- 256 MB zone — plus 2N+1 SSH/exec round trips — every sweep. On a 50-domain box
324
+ -- that is 101 full scans and 101 round trips, on a schedule, to collect data that
325
+ -- one pass already has in hand.
326
+ --
327
+ -- So: one `get_keys(0)`, classify every key by (kind, domain) in a single pass, then
328
+ -- serve all domains from the in-memory index. 2N+1 scans → 1, and 2N+1 round trips
329
+ -- → 1. The per-domain GET endpoints stay for the READ path, where a page view wants
330
+ -- one domain's live tail and a single scan is the honest cost.
331
+ --
332
+ -- Flush semantics are unchanged and still atomic per key: minute counters and their
333
+ -- country keys are deleted as they are read, so a bucket is handed to exactly one
334
+ -- caller no matter how many collectors race. The daily rollup is read-only — the
335
+ -- edge keeps running totals for the day, and deleting them would lose the totals for
336
+ -- every later scrape of the same day.
337
+
338
+ if uri == "/analytics/collect" and ngx.req.get_method() == "POST" then
339
+ ngx.req.read_body()
340
+ local body = ngx.req.get_body_data()
341
+ if not body then return bad("empty body") end
342
+ local payload = cjson.decode(body)
343
+ if type(payload) ~= "table" or type(payload.domains) ~= "table" then
344
+ return bad('expected { day, domains: [{domain, from, to}] }')
345
+ end
346
+
347
+ local day = payload.day
348
+ if type(day) ~= "string" or #day ~= 8 then day = os.date("!%Y%m%d") end
349
+
350
+ -- Requested domains → their minute window. Also the membership test the scan
351
+ -- uses, so an unrequested domain's keys are skipped without a string compare
352
+ -- per domain.
353
+ local want, order = {}, {}
354
+ for i = 1, #payload.domains do
355
+ local d = payload.domains[i]
356
+ if type(d) == "table" and type(d.domain) == "string" and d.domain ~= "" then
357
+ local name = d.domain:lower()
358
+ local from_m, to_m = tonumber(d.from), tonumber(d.to)
359
+ if from_m and to_m then
360
+ if to_m - from_m > 1440 then to_m = from_m + 1440 end -- cap at 24h
361
+ if not want[name] then order[#order + 1] = name end
362
+ want[name] = { from = from_m, to = to_m }
363
+ end
364
+ end
365
+ end
366
+ if #order == 0 then return json({ domains = {} }) end
367
+
368
+ -- ── Per-domain index reads ───────────────────────────────────────────────
369
+ --
370
+ -- Was a single `get_keys(0)` pass classifying every key in the zone. One scan is better
371
+ -- than 2N+1, but it still walked keys belonging to domains nobody asked about, and held
372
+ -- the zone lock while doing it. Now each requested domain reads only its own indexes.
373
+ local countries = {} -- domain → minute → CC → dict key
374
+ local rollups = {} -- domain → suffix → value
375
+ for i = 1, #order do
376
+ local domain = order[i]
377
+ local w = want[domain]
378
+ countries[domain] = scan_country_index(domain, w.from, w.to)
379
+ rollups[domain] = read_rollup(domain, day)
380
+ end
381
+
382
+ -- ── Serve each domain from the index ─────────────────────────────────────
383
+ local out = {}
384
+ for i = 1, #order do
385
+ local domain = order[i]
386
+ local w = want[domain]
387
+ local cidx = countries[domain] or {}
388
+
389
+ local buckets, flushed = {}, 0
390
+ for m = w.from, w.to do
391
+ local p = "s:" .. domain .. ":" .. m
392
+ -- Drain FIRST, then decide: `drain` returns what it took, and a counter left
393
+ -- at 0 by an earlier flush must not emit a bucket of zeros. (Zero is truthy in
394
+ -- Lua, so the old `if r then` would have started doing exactly that the moment
395
+ -- keys stopped being deleted.)
396
+ local r = drain(p .. ":r")
397
+ if r > 0 then
398
+ local rt_us = drain(p .. ":t")
399
+ buckets[#buckets + 1] = {
400
+ minute = m,
401
+ requests = r,
402
+ unique_requests = drain(p .. ":u"),
403
+ bandwidth_in = drain(p .. ":i"),
404
+ bandwidth_out = drain(p .. ":o"),
405
+ response_time = rt_us / 1000000,
406
+ countries = read_minute_countries(cidx[m], true),
407
+ }
408
+ flushed = flushed + 1
409
+ end
410
+ end
411
+
412
+ -- Daily rollup, same key classification the single-domain endpoint uses.
413
+ local geo, paths, statuses, visitors = {}, {}, {}, 0
414
+ for suffix, v in pairs(rollups[domain] or {}) do
415
+ if suffix == "v" then
416
+ visitors = v
417
+ elseif suffix:sub(1, 2) == "p:" then
418
+ paths[suffix:sub(3)] = v
419
+ elseif suffix:sub(1, 2) == "s:" then
420
+ statuses[suffix:sub(3)] = v
421
+ elseif suffix:match("^%u%u$") then
422
+ geo[suffix] = v
423
+ end
424
+ -- Anything else (`pn`) is bookkeeping: never reported as a country.
425
+ end
426
+
427
+ out[domain] = {
428
+ buckets = buckets,
429
+ flushed = flushed,
430
+ rollup = {
431
+ day = day, countries = geo, visitors = visitors,
432
+ paths = paths, statuses = statuses,
433
+ },
434
+ }
435
+ end
436
+
437
+ return json({ day = day, domains = out })
438
+ end
439
+
440
+ -- ── GET /analytics - minute-bucket time series ───────────────────────────────
441
+ -- ?domain=example.com&from=EPOCH_MIN&to=EPOCH_MIN
442
+
443
+ if uri == "/analytics" then
444
+ local domain = ngx.var.arg_domain
445
+ if not domain or domain == "" then return bad("missing ?domain=") end
446
+ domain = domain:lower()
447
+
448
+ local from_m = tonumber(ngx.var.arg_from)
449
+ local to_m = tonumber(ngx.var.arg_to)
450
+ if not from_m or not to_m then
451
+ return bad("missing ?from= and ?to= (epoch minutes)")
452
+ end
453
+ -- Cap at 24h
454
+ if to_m - from_m > 1440 then to_m = from_m + 1440 end
455
+
456
+ -- ONE scan for the whole window (see scan_country_index).
457
+ local country_index = scan_country_index(domain, from_m, to_m)
458
+
459
+ local buckets = {}
460
+ for m = from_m, to_m do
461
+ local p = "s:" .. domain .. ":" .. m
462
+ local r = analytics:get(p .. ":r")
463
+ if r then
464
+ -- Response time stored as microseconds, convert to seconds (float)
465
+ local rt_us = analytics:get(p .. ":t") or 0
466
+ buckets[#buckets + 1] = {
467
+ minute = m,
468
+ requests = r,
469
+ unique_requests = analytics:get(p .. ":u") or 0,
470
+ bandwidth_in = analytics:get(p .. ":i") or 0,
471
+ bandwidth_out = analytics:get(p .. ":o") or 0,
472
+ response_time = rt_us / 1000000,
473
+ countries = read_minute_countries(country_index[m], false),
474
+ }
475
+ end
476
+ end
477
+
478
+ return json({ domain = domain, buckets = buckets })
479
+ end
480
+
481
+ -- ── POST /analytics/flush - read + delete minute buckets ─────────────────────
482
+ -- Same as GET /analytics but deletes the returned buckets from shared memory.
483
+ -- Used by the scraper to atomically move data from OpenResty → DB.
484
+ -- ?domain=example.com&from=EPOCH_MIN&to=EPOCH_MIN
485
+
486
+ if uri == "/analytics/flush" and ngx.req.get_method() == "POST" then
487
+ local domain = ngx.var.arg_domain
488
+ if not domain or domain == "" then return bad("missing ?domain=") end
489
+ domain = domain:lower()
490
+
491
+ local from_m = tonumber(ngx.var.arg_from)
492
+ local to_m = tonumber(ngx.var.arg_to)
493
+ if not from_m or not to_m then
494
+ return bad("missing ?from= and ?to= (epoch minutes)")
495
+ end
496
+ if to_m - from_m > 1440 then to_m = from_m + 1440 end
497
+
498
+ -- ONE scan for the whole window (see scan_country_index).
499
+ local country_index = scan_country_index(domain, from_m, to_m)
500
+
501
+ local buckets = {}
502
+ local flushed = 0
503
+ for m = from_m, to_m do
504
+ local p = "s:" .. domain .. ":" .. m
505
+ -- Drain, don't delete — see `drain`. A counter already at 0 is not a bucket.
506
+ local r = drain(p .. ":r")
507
+ if r > 0 then
508
+ local rt_us = drain(p .. ":t")
509
+ buckets[#buckets + 1] = {
510
+ minute = m,
511
+ requests = r,
512
+ unique_requests = drain(p .. ":u"),
513
+ bandwidth_in = drain(p .. ":i"),
514
+ bandwidth_out = drain(p .. ":o"),
515
+ response_time = rt_us / 1000000,
516
+ -- Drained too: this endpoint hands the minute to the DB, so the country
517
+ -- keys must go with it or they would be counted twice.
518
+ countries = read_minute_countries(country_index[m], true),
519
+ }
520
+ flushed = flushed + 1
521
+ end
522
+ end
523
+
524
+ return json({ domain = domain, buckets = buckets, flushed = flushed })
525
+ end
526
+
527
+ -- ── GET /analytics/totals - lifetime counters ────────────────────────────────
528
+ -- ?domain=example.com → single domain
529
+ -- (no domain) → all known domains
530
+
531
+ if uri == "/analytics/totals" then
532
+ local domain = ngx.var.arg_domain
533
+
534
+ if not domain or domain == "" then
535
+ -- The scraper's domain-discovery call. Read from the `dn`/`di:` index that
536
+ -- site_logger appends to the first time it ever sees a host, so this no longer
537
+ -- walks the zone. It was the worst place for a scan: a cap here silently dropped
538
+ -- whole domains from every subsequent flush.
539
+ local domains = {}
540
+ for _, d in ipairs(read_index("dn", "di:")) do
541
+ -- A host can be indexed but have had its lifetime totals evicted; report it
542
+ -- with zeros rather than omitting it, so the scraper still asks for its buckets.
543
+ domains[#domains + 1] = {
544
+ domain = d,
545
+ requests = analytics:get("t:" .. d .. ":r") or 0,
546
+ bandwidth_in = analytics:get("t:" .. d .. ":i") or 0,
547
+ bandwidth_out = analytics:get("t:" .. d .. ":o") or 0,
548
+ }
549
+ end
550
+ return json({ domains = domains })
551
+ end
552
+
553
+ domain = domain:lower()
554
+ return json({
555
+ domain = domain,
556
+ requests = analytics:get("t:" .. domain .. ":r") or 0,
557
+ bandwidth_in = analytics:get("t:" .. domain .. ":i") or 0,
558
+ bandwidth_out = analytics:get("t:" .. domain .. ":o") or 0,
559
+ })
560
+ end
561
+
562
+ -- ── GET /analytics/geo - daily rollup for a day ──────────────────────────────
563
+ -- ?domain=example.com&day=YYYYMMDD
564
+ --
565
+ -- Named /geo for compatibility; it now returns the whole daily rollup — countries,
566
+ -- distinct visitors, top paths, status-code mix. One scan serves all four: they
567
+ -- share the `g:{domain}:{day}:` prefix precisely so a reader never needs a second
568
+ -- pass over the zone.
569
+ --
570
+ -- Key layout under that prefix (written by site_logger.lua):
571
+ -- <CC> EXACTLY two uppercase letters → country hit count
572
+ -- v distinct visitors today
573
+ -- p:<path> path counter
574
+ -- pn distinct-path counter (cardinality cap bookkeeping)
575
+ -- s:<status> status-code counter
576
+ --
577
+ -- Country matching is strict (^%u%u$) rather than "whatever's left": a bookkeeping
578
+ -- key like `pn` would otherwise surface as a country named "pn" and get plotted.
579
+
580
+ if uri == "/analytics/geo" then
581
+ local domain = ngx.var.arg_domain
582
+ if not domain or domain == "" then return bad("missing ?domain=") end
583
+ domain = domain:lower()
584
+
585
+ local day = ngx.var.arg_day
586
+ if not day or #day ~= 8 then
587
+ day = os.date("!%Y%m%d")
588
+ end
589
+
590
+ -- Read through the write-time indexes — same helper /analytics/collect uses, so the two
591
+ -- endpoints cannot disagree about what a day contains. Replaces a `get_keys(0)` that
592
+ -- walked the whole zone to find keys under one prefix.
593
+ local rollup = read_rollup(domain, day)
594
+ local countries, paths, statuses = {}, {}, {}
595
+ local visitors = tonumber(rollup["v"]) or 0
596
+ for suffix, v in pairs(rollup) do
597
+ if suffix == "v" or suffix == "pn" then
598
+ -- bookkeeping, not data
599
+ elseif suffix:sub(1, 2) == "p:" then
600
+ paths[suffix:sub(3)] = v
601
+ elseif suffix:sub(1, 2) == "s:" then
602
+ statuses[suffix:sub(3)] = v
603
+ elseif suffix:match("^%u%u$") then
604
+ -- Country codes only. Anything else under this prefix is bookkeeping, and
605
+ -- letting one through made `pn` plot as a country and skew every percentage.
606
+ countries[suffix] = v
607
+ end
608
+ end
609
+ return json({
610
+ domain = domain,
611
+ day = day,
612
+ countries = countries,
613
+ visitors = visitors,
614
+ paths = paths,
615
+ statuses = statuses,
616
+ })
617
+ end
618
+
619
+ -- ── GET /logs/recent - raw request ring buffer ───────────────────────────────
620
+ -- ?domain=example.com&limit=50
621
+
622
+ if uri == "/logs/recent" then
623
+ if not request_data then
624
+ return json({ error = "request_data dict unavailable" }, 503)
625
+ end
626
+
627
+ local domain = ngx.var.arg_domain
628
+ if not domain or domain == "" then return bad("missing ?domain=") end
629
+ domain = domain:lower()
630
+
631
+ local limit = math.min(tonumber(ngx.var.arg_limit) or 50, 1000)
632
+ local seq = request_data:get("rlog:" .. domain .. ":seq") or 0
633
+ local out = {}
634
+
635
+ for i = 0, limit - 1 do
636
+ local slot = (seq - i) % 1000
637
+ if slot < 0 then slot = slot + 1000 end
638
+ local raw = request_data:get("rlog:" .. domain .. ":" .. slot)
639
+ if raw then out[#out + 1] = raw end
640
+ end
641
+
642
+ -- Return pre-encoded JSON entries as a JSON array
643
+ ngx.header["Content-Type"] = "application/json"
644
+ ngx.print("[")
645
+ for idx, entry in ipairs(out) do
646
+ if idx > 1 then ngx.print(",") end
647
+ ngx.print(entry)
648
+ end
649
+ ngx.say("]")
650
+ return ngx.exit(200)
651
+ end
652
+
653
+ -- ── 404 ──────────────────────────────────────────────────────────────────────
654
+ return json({ error = "not found" }, 404)