@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.
- package/README.md +263 -0
- package/dist/bare-HL2GUYE2.js +27 -0
- package/dist/bowser-HNOTPGJO.js +2825 -0
- package/dist/chunk-3CJLKL6E.js +124 -0
- package/dist/chunk-3NQQXMDR.js +2098 -0
- package/dist/chunk-42WQABXM.js +439 -0
- package/dist/chunk-5GPXMTE4.js +25 -0
- package/dist/chunk-AJ4QZLZ2.js +1069 -0
- package/dist/chunk-AO252K7Z.js +137 -0
- package/dist/chunk-BQMFIXHI.js +975 -0
- package/dist/chunk-DM3PKWDC.js +2453 -0
- package/dist/chunk-DOD2BXHQ.js +1364 -0
- package/dist/chunk-DRK3MEDS.js +2870 -0
- package/dist/chunk-EW4PHKB2.js +13914 -0
- package/dist/chunk-HMJUFZK7.js +340 -0
- package/dist/chunk-I2TOOF2A.js +517 -0
- package/dist/chunk-I3DPMTSF.js +29 -0
- package/dist/chunk-JSCZT76F.js +2588 -0
- package/dist/chunk-MNV2QFHW.js +138 -0
- package/dist/chunk-MOLPYPVH.js +2529 -0
- package/dist/chunk-N4OALTFK.js +295 -0
- package/dist/chunk-OFXBNFNL.js +458 -0
- package/dist/chunk-ONKEYSMY.js +1388 -0
- package/dist/chunk-PDW4L3NV.js +17 -0
- package/dist/chunk-PNNXWA2A.js +1947 -0
- package/dist/chunk-PNRPKRJU.js +11105 -0
- package/dist/chunk-PVOWHIND.js +26781 -0
- package/dist/chunk-PXKCZB2I.js +46 -0
- package/dist/chunk-PZVKPCKE.js +475 -0
- package/dist/chunk-QWHBRY2K.js +4945 -0
- package/dist/chunk-RFOBMHQ7.js +338 -0
- package/dist/chunk-RHLNVZMS.js +28 -0
- package/dist/chunk-RVER2EOO.js +577 -0
- package/dist/chunk-S3QBGGVP.js +81 -0
- package/dist/chunk-TAML5WMA.js +198 -0
- package/dist/chunk-THTEUDBC.js +3188 -0
- package/dist/chunk-WYNM7L22.js +329 -0
- package/dist/chunk-XGCG4MD5.js +102 -0
- package/dist/chunk-XVKUFMIB.js +148 -0
- package/dist/chunk-XZYFZ6A5.js +50 -0
- package/dist/chunk-Y7PVJUXJ.js +2275 -0
- package/dist/chunk-YRSZPVMB.js +306 -0
- package/dist/chunk-YZUKHG3S.js +362 -0
- package/dist/chunk-ZVFS4LUA.js +116 -0
- package/dist/chunk-ZWLDLTJP.js +1417 -0
- package/dist/detect-676SNZK4.js +48 -0
- package/dist/dist-es-2YRDXMUU.js +73 -0
- package/dist/dist-es-CCVZEFRZ.js +91 -0
- package/dist/dist-es-EDUSMF6Q.js +488 -0
- package/dist/dist-es-IOH6G4GM.js +380 -0
- package/dist/dist-es-KWSMDPIP.js +26 -0
- package/dist/dist-es-UYDDOVZ3.js +173 -0
- package/dist/dist-es-XUOR4NQN.js +321 -0
- package/dist/docker-GOT3Q7DJ.js +41 -0
- package/dist/docker-edge-executor-VO6JHHW2.js +19 -0
- package/dist/edge-import-NFCHIW7G.js +50 -0
- package/dist/edge-preflight-QUJ757C6.js +45 -0
- package/dist/ensure-container-edge-Q7IZXHGM.js +38 -0
- package/dist/event-streams-TR4GAC53.js +46 -0
- package/dist/executor-GKXKN52K.js +42 -0
- package/dist/host-channel-preflight-Z6HYSPKO.js +48 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9469 -0
- package/dist/loadSso-UGF4JMRK.js +581 -0
- package/dist/local-executor-OMMDKXCM.js +13 -0
- package/dist/nginx-VITTWZZU.js +42 -0
- package/dist/node-bootstrap.js +277 -0
- package/dist/node-entry.js +55 -0
- package/dist/noop-CLLJHCPL.js +11 -0
- package/dist/openresty-lua-6I3Z6PEQ.js +90 -0
- package/dist/privilege-UQAI2HV4.js +20 -0
- package/dist/server/engine/ChangeLog +2289 -0
- package/dist/server/engine/Documentations +5 -0
- package/dist/server/engine/LICENSE +674 -0
- package/dist/server/engine/README.md +34 -0
- package/dist/server/engine/conf/amavisd +150 -0
- package/dist/server/engine/conf/clamav +71 -0
- package/dist/server/engine/conf/core +632 -0
- package/dist/server/engine/conf/dovecot +151 -0
- package/dist/server/engine/conf/fail2ban +72 -0
- package/dist/server/engine/conf/global +698 -0
- package/dist/server/engine/conf/iredapd +52 -0
- package/dist/server/engine/conf/logwatch +28 -0
- package/dist/server/engine/conf/postfix +97 -0
- package/dist/server/engine/conf/postgresql +93 -0
- package/dist/server/engine/conf/spamassassin +39 -0
- package/dist/server/engine/dialog/config_via_dialog.sh +258 -0
- package/dist/server/engine/dialog/optional_components.sh +122 -0
- package/dist/server/engine/dialog/pgsql_config.sh +52 -0
- package/dist/server/engine/dialog/virtual_domain_config.sh +94 -0
- package/dist/server/engine/functions/amavisd.sh +267 -0
- package/dist/server/engine/functions/backend.sh +85 -0
- package/dist/server/engine/functions/clamav.sh +100 -0
- package/dist/server/engine/functions/cleanup.sh +393 -0
- package/dist/server/engine/functions/dovecot.sh +583 -0
- package/dist/server/engine/functions/fail2ban.sh +209 -0
- package/dist/server/engine/functions/iredapd.sh +356 -0
- package/dist/server/engine/functions/optional_components.sh +24 -0
- package/dist/server/engine/functions/packages.sh +522 -0
- package/dist/server/engine/functions/packages_freebsd.sh +179 -0
- package/dist/server/engine/functions/postfix.sh +457 -0
- package/dist/server/engine/functions/postgresql.sh +239 -0
- package/dist/server/engine/functions/spamassassin.sh +49 -0
- package/dist/server/engine/functions/system_accounts.sh +128 -0
- package/dist/server/engine/iRedMail.sh +144 -0
- package/dist/server/engine/pkgs/get_all.sh +338 -0
- package/dist/server/engine/pkgs/pkgs.freebsd.sha256 +3 -0
- package/dist/server/engine/pkgs/pkgs.openbsd.sha256 +6 -0
- package/dist/server/engine/pkgs/pkgs.sha256 +1 -0
- package/dist/server/engine/samples/amavisd/amavisd.conf +702 -0
- package/dist/server/engine/samples/amavisd/amavisd.mysql +278 -0
- package/dist/server/engine/samples/amavisd/amavisd.pgsql +253 -0
- package/dist/server/engine/samples/amavisd/amavisd.schema +942 -0
- package/dist/server/engine/samples/amavisd/default_spam_policy.sql +46 -0
- package/dist/server/engine/samples/dovecot/dovecot-2.4-mariadb.conf +758 -0
- package/dist/server/engine/samples/dovecot/dovecot-2.4-openldap.conf +767 -0
- package/dist/server/engine/samples/dovecot/dovecot-2.4-pgsql.conf +762 -0
- package/dist/server/engine/samples/dovecot/dovecot-last-login.conf +50 -0
- package/dist/server/engine/samples/dovecot/dovecot-ldap.conf +21 -0
- package/dist/server/engine/samples/dovecot/dovecot-share-folder.conf +22 -0
- package/dist/server/engine/samples/dovecot/dovecot-sql.conf +28 -0
- package/dist/server/engine/samples/dovecot/dovecot-used-quota.conf +13 -0
- package/dist/server/engine/samples/dovecot/dovecot.conf +484 -0
- package/dist/server/engine/samples/dovecot/dovecot.sieve +7 -0
- package/dist/server/engine/samples/dovecot/dovecot2-quota-warning.sh +38 -0
- package/dist/server/engine/samples/dovecot/sql/imap_share_folder.mysql +16 -0
- package/dist/server/engine/samples/dovecot/sql/last_login.mysql +12 -0
- package/dist/server/engine/samples/dovecot/sql/used_quota.mysql +26 -0
- package/dist/server/engine/samples/dovecot/systemd/override.conf +3 -0
- package/dist/server/engine/samples/fail2ban/action.d/banned_db.conf +9 -0
- package/dist/server/engine/samples/fail2ban/bin/fail2ban_banned_db +268 -0
- package/dist/server/engine/samples/fail2ban/fail2ban.local +17 -0
- package/dist/server/engine/samples/fail2ban/filter.d/dovecot.iredmail.conf +5 -0
- package/dist/server/engine/samples/fail2ban/filter.d/postfix-pregreet.iredmail.conf +16 -0
- package/dist/server/engine/samples/fail2ban/filter.d/postfix.iredmail.conf +18 -0
- package/dist/server/engine/samples/fail2ban/filter.d/roundcube.iredmail.conf +9 -0
- package/dist/server/engine/samples/fail2ban/jail.d/dovecot.local +8 -0
- package/dist/server/engine/samples/fail2ban/jail.d/nginx-http-auth.local +8 -0
- package/dist/server/engine/samples/fail2ban/jail.d/postfix-pregreet.local +9 -0
- package/dist/server/engine/samples/fail2ban/jail.d/postfix.local +8 -0
- package/dist/server/engine/samples/fail2ban/jail.d/roundcube.local +9 -0
- package/dist/server/engine/samples/fail2ban/jail.d/sogo.local +8 -0
- package/dist/server/engine/samples/fail2ban/jail.d/sshd.local +11 -0
- package/dist/server/engine/samples/fail2ban/jail.local +6 -0
- package/dist/server/engine/samples/fail2ban/openbsd/rc +27 -0
- package/dist/server/engine/samples/fail2ban/sql/fail2ban.mysql +49 -0
- package/dist/server/engine/samples/fail2ban/sql/fail2ban.pgsql +48 -0
- package/dist/server/engine/samples/firewall/firewalld/services/ssh.xml +6 -0
- package/dist/server/engine/samples/firewall/firewalld/zones/iredmail.xml +15 -0
- package/dist/server/engine/samples/firewall/iptables/ip6tables.init.debian +362 -0
- package/dist/server/engine/samples/firewall/iptables/ip6tables.rules +53 -0
- package/dist/server/engine/samples/firewall/iptables/iptables.init.debian +363 -0
- package/dist/server/engine/samples/firewall/iptables/iptables.rules +54 -0
- package/dist/server/engine/samples/firewall/nftables.conf +58 -0
- package/dist/server/engine/samples/freebsd/newsyslog.conf.d/dovecot.conf +3 -0
- package/dist/server/engine/samples/freebsd/newsyslog.conf.d/iredapd.conf +1 -0
- package/dist/server/engine/samples/freebsd/newsyslog.conf.d/mlmmjadmin.conf +1 -0
- package/dist/server/engine/samples/freebsd/newsyslog.conf.d/php-fpm.conf +2 -0
- package/dist/server/engine/samples/freebsd/newsyslog.conf.d/slapd.conf +1 -0
- package/dist/server/engine/samples/freebsd/syslog.d/dovecot.conf +2 -0
- package/dist/server/engine/samples/freebsd/syslog.d/fail2ban.conf +2 -0
- package/dist/server/engine/samples/freebsd/syslog.d/iredapd.conf +2 -0
- package/dist/server/engine/samples/freebsd/syslog.d/mlmmjadmin.conf +2 -0
- package/dist/server/engine/samples/freebsd/syslog.d/php-fpm.conf +2 -0
- package/dist/server/engine/samples/freebsd/syslog.d/slapd.conf +2 -0
- package/dist/server/engine/samples/iredmail/iredmail.mysql +491 -0
- package/dist/server/engine/samples/iredmail/iredmail.pgsql +465 -0
- package/dist/server/engine/samples/iredmail/iredmail.schema +456 -0
- package/dist/server/engine/samples/iredmail/used_quota_triggers_dovecot_2.3.pgsql +40 -0
- package/dist/server/engine/samples/iredmail/used_quota_triggers_dovecot_2.4.pgsql +18 -0
- package/dist/server/engine/samples/logrotate/dovecot +13 -0
- package/dist/server/engine/samples/logrotate/iredapd +13 -0
- package/dist/server/engine/samples/logrotate/mlmmjadmin +13 -0
- package/dist/server/engine/samples/logrotate/openldap +19 -0
- package/dist/server/engine/samples/logrotate/php-fpm +13 -0
- package/dist/server/engine/samples/openbsd/ldapd.conf +58 -0
- package/dist/server/engine/samples/openbsd/pf.conf +28 -0
- package/dist/server/engine/samples/postfix/command_filter.pcre +3 -0
- package/dist/server/engine/samples/postfix/freebsd/mailer.conf +9 -0
- package/dist/server/engine/samples/postfix/helo_access.pcre +180 -0
- package/dist/server/engine/samples/postfix/ldap/catchall_maps.cf +11 -0
- package/dist/server/engine/samples/postfix/ldap/recipient_bcc_maps_domain.cf +11 -0
- package/dist/server/engine/samples/postfix/ldap/recipient_bcc_maps_user.cf +11 -0
- package/dist/server/engine/samples/postfix/ldap/relay_domains.cf +11 -0
- package/dist/server/engine/samples/postfix/ldap/sender_bcc_maps_domain.cf +11 -0
- package/dist/server/engine/samples/postfix/ldap/sender_bcc_maps_user.cf +11 -0
- package/dist/server/engine/samples/postfix/ldap/sender_dependent_relayhost_maps_domain.cf +11 -0
- package/dist/server/engine/samples/postfix/ldap/sender_dependent_relayhost_maps_user.cf +11 -0
- package/dist/server/engine/samples/postfix/ldap/sender_login_maps.cf +11 -0
- package/dist/server/engine/samples/postfix/ldap/transport_maps_domain.cf +11 -0
- package/dist/server/engine/samples/postfix/ldap/transport_maps_user.cf +11 -0
- package/dist/server/engine/samples/postfix/ldap/virtual_alias_maps.cf +11 -0
- package/dist/server/engine/samples/postfix/ldap/virtual_group_maps.cf +11 -0
- package/dist/server/engine/samples/postfix/ldap/virtual_group_members_maps.cf +11 -0
- package/dist/server/engine/samples/postfix/ldap/virtual_mailbox_domains.cf +11 -0
- package/dist/server/engine/samples/postfix/ldap/virtual_mailbox_maps.cf +12 -0
- package/dist/server/engine/samples/postfix/main.cf +282 -0
- package/dist/server/engine/samples/postfix/main.cf.amavisd +7 -0
- package/dist/server/engine/samples/postfix/main.cf.dovecot +8 -0
- package/dist/server/engine/samples/postfix/main.cf.ldap +39 -0
- package/dist/server/engine/samples/postfix/main.cf.mlmmj +5 -0
- package/dist/server/engine/samples/postfix/main.cf.mysql +39 -0
- package/dist/server/engine/samples/postfix/main.cf.pgsql +39 -0
- package/dist/server/engine/samples/postfix/main.cf.postscreen +30 -0
- package/dist/server/engine/samples/postfix/master.cf +101 -0
- package/dist/server/engine/samples/postfix/mysql/catchall_maps.cf +5 -0
- package/dist/server/engine/samples/postfix/mysql/domain_alias_catchall_maps.cf +5 -0
- package/dist/server/engine/samples/postfix/mysql/domain_alias_maps.cf +5 -0
- package/dist/server/engine/samples/postfix/mysql/recipient_bcc_maps_domain.cf +5 -0
- package/dist/server/engine/samples/postfix/mysql/recipient_bcc_maps_user.cf +5 -0
- package/dist/server/engine/samples/postfix/mysql/relay_domains.cf +5 -0
- package/dist/server/engine/samples/postfix/mysql/sender_bcc_maps_domain.cf +5 -0
- package/dist/server/engine/samples/postfix/mysql/sender_bcc_maps_user.cf +5 -0
- package/dist/server/engine/samples/postfix/mysql/sender_dependent_relayhost_maps.cf +6 -0
- package/dist/server/engine/samples/postfix/mysql/sender_login_maps.cf +5 -0
- package/dist/server/engine/samples/postfix/mysql/transport_maps_domain.cf +5 -0
- package/dist/server/engine/samples/postfix/mysql/transport_maps_maillist.cf +5 -0
- package/dist/server/engine/samples/postfix/mysql/transport_maps_user.cf +5 -0
- package/dist/server/engine/samples/postfix/mysql/virtual_alias_maps.cf +5 -0
- package/dist/server/engine/samples/postfix/mysql/virtual_mailbox_domains.cf +5 -0
- package/dist/server/engine/samples/postfix/mysql/virtual_mailbox_maps.cf +5 -0
- package/dist/server/engine/samples/postfix/pgsql/catchall_maps.cf +5 -0
- package/dist/server/engine/samples/postfix/pgsql/domain_alias_catchall_maps.cf +5 -0
- package/dist/server/engine/samples/postfix/pgsql/domain_alias_maps.cf +5 -0
- package/dist/server/engine/samples/postfix/pgsql/recipient_bcc_maps_domain.cf +5 -0
- package/dist/server/engine/samples/postfix/pgsql/recipient_bcc_maps_user.cf +5 -0
- package/dist/server/engine/samples/postfix/pgsql/relay_domains.cf +5 -0
- package/dist/server/engine/samples/postfix/pgsql/sender_bcc_maps_domain.cf +5 -0
- package/dist/server/engine/samples/postfix/pgsql/sender_bcc_maps_user.cf +5 -0
- package/dist/server/engine/samples/postfix/pgsql/sender_dependent_relayhost_maps.cf +6 -0
- package/dist/server/engine/samples/postfix/pgsql/sender_login_maps.cf +5 -0
- package/dist/server/engine/samples/postfix/pgsql/transport_maps_domain.cf +5 -0
- package/dist/server/engine/samples/postfix/pgsql/transport_maps_maillist.cf +5 -0
- package/dist/server/engine/samples/postfix/pgsql/transport_maps_user.cf +5 -0
- package/dist/server/engine/samples/postfix/pgsql/virtual_alias_maps.cf +5 -0
- package/dist/server/engine/samples/postfix/pgsql/virtual_mailbox_domains.cf +5 -0
- package/dist/server/engine/samples/postfix/pgsql/virtual_mailbox_maps.cf +5 -0
- package/dist/server/engine/samples/postfix/postscreen_access.cidr +6 -0
- package/dist/server/engine/samples/postgresql/sql/add_first_domain_and_user.sql +34 -0
- package/dist/server/engine/samples/postgresql/sql/grant_permissions.sql +14 -0
- package/dist/server/engine/samples/postgresql/sql/init_vmail_db.sql +15 -0
- package/dist/server/engine/samples/rsyslog.d/1-iredmail-dovecot.conf +23 -0
- package/dist/server/engine/samples/rsyslog.d/1-iredmail-fail2ban.conf +2 -0
- package/dist/server/engine/samples/rsyslog.d/1-iredmail-iredapd.conf +12 -0
- package/dist/server/engine/samples/rsyslog.d/1-iredmail-mlmmjadmin.conf +12 -0
- package/dist/server/engine/samples/rsyslog.d/1-iredmail-openldap.conf +2 -0
- package/dist/server/engine/samples/rsyslog.d/1-iredmail-phpfpm.conf +3 -0
- package/dist/server/engine/samples/spamassassin/local.cf +186 -0
- package/dist/server/engine/samples/spamassassin/razor.conf +1 -0
- package/dist/server/engine/samples/systemd/clamd.service.d/override.conf +2 -0
- package/dist/server/engine/samples/systemd/mariadb.service.d/override.conf +3 -0
- package/dist/server/engine/samples/systemd/slapd.service.d/override.conf +6 -0
- package/dist/server/engine/samples/yum/CentOS-Linux-AppStream.repo +6 -0
- package/dist/server/engine/samples/yum/CentOS-Linux-PowerTools.repo +6 -0
- package/dist/server/engine/samples/yum/CentOS-Stream-AppStream.repo +7 -0
- package/dist/server/engine/samples/yum/CentOS-Stream-PowerTools.repo +7 -0
- package/dist/server/engine/samples/yum/symas-openldap.repo +6 -0
- package/dist/server/engine/tools/add_enabled_service.py +60 -0
- package/dist/server/engine/tools/backup_mysql.sh +238 -0
- package/dist/server/engine/tools/backup_openldap.sh +230 -0
- package/dist/server/engine/tools/backup_pgsql.sh +217 -0
- package/dist/server/engine/tools/backup_sogo.sh +156 -0
- package/dist/server/engine/tools/create_mail_user_OpenLDAP.py +335 -0
- package/dist/server/engine/tools/create_mail_user_OpenLDAP.sh +287 -0
- package/dist/server/engine/tools/create_mail_user_SQL.sh +127 -0
- package/dist/server/engine/tools/enable_postscreen.sh +144 -0
- package/dist/server/engine/tools/fail2ban_unban_ip.sh +39 -0
- package/dist/server/engine/tools/find_sasl_login_ip.sh +20 -0
- package/dist/server/engine/tools/find_top_sasl_usernames.sh +28 -0
- package/dist/server/engine/tools/generate_ssl_keys.sh +85 -0
- package/dist/server/engine/tools/kill_high_cpu_sogo_process.sh +32 -0
- package/dist/server/engine/tools/ldap_assign_user_to_groups.py +151 -0
- package/dist/server/engine/tools/ldap_move_members_to_another_group.py +129 -0
- package/dist/server/engine/tools/migrate_sql_alias_table.py +177 -0
- package/dist/server/engine/update/0.9.8/amavisd.mysql +29 -0
- package/dist/server/engine/update/0.9.8/amavisd.pgsql +39 -0
- package/dist/server/engine/update/0.9.8/iredmail.mysql +53 -0
- package/dist/server/engine/update/0.9.8/iredmail.pgsql +61 -0
- package/dist/server/engine/update/0.9.9/iredmail.mysql +2 -0
- package/dist/server/engine/update/0.9.9/iredmail.pgsql +2 -0
- package/dist/server/engine/update/1.0/iredmail.mysql +2 -0
- package/dist/server/engine/update/1.0/iredmail.pgsql +2 -0
- package/dist/server/engine/update/1.2/last_login.mysql +16 -0
- package/dist/server/engine/update/1.4.0/iredmail.mysql +22 -0
- package/dist/server/engine/update/1.4.0/iredmail.pgsql +20 -0
- package/dist/server/engine/update/1.4.1/iredmail.mysql +3 -0
- package/dist/server/engine/update/1.4.1/iredmail.pgsql +3 -0
- package/dist/server/engine/update/1.4.1/sogo.mysql +11 -0
- package/dist/server/engine/update/1.4.1/sogo.pgsql +30 -0
- package/dist/server/engine/update/1.4.1/update-ldap.py +66 -0
- package/dist/server/engine/update/1.4.2/iredmail.mysql +9 -0
- package/dist/server/engine/update/1.4.2/iredmail.pgsql +9 -0
- package/dist/server/engine/update/1.4.2/sogo.pgsql +30 -0
- package/dist/server/engine/update/1.6.0/sogo_view.pgsql +27 -0
- package/dist/server/engine/update/1.6.3/iredmail.mysql +1 -0
- package/dist/server/engine/update/1.7.0/fail2ban.mysql +11 -0
- package/dist/server/engine/update/1.7.0/fail2ban.pgsql +12 -0
- package/dist/server/engine/update/1.7.1/amavisd.mysql +1 -0
- package/dist/server/engine/update/1.7.2/vmail.mysql +30 -0
- package/dist/server/engine/update/1.7.3/deleted_mailboxes.mysql +43 -0
- package/dist/server/engine/update/1.7.3/iredadmin.mysql +26 -0
- package/dist/server/engine/update/1.7.3/vmail.mysql +79 -0
- package/dist/server/engine/update/1.7.3/vmail.pgsql +9 -0
- package/dist/server/engine/update/README.md +1 -0
- package/dist/server/engine/update/ldap/README.md +39 -0
- package/dist/server/engine/update/ldap/update-ldap-dovecot-2.3.py +69 -0
- package/dist/server/index.js +349016 -0
- package/dist/server/lua/geo_country.lua +127 -0
- package/dist/server/lua/maxminddb.lua +403 -0
- package/dist/server/lua/mgmt_api.lua +654 -0
- package/dist/server/lua/pipe_stream.lua +103 -0
- package/dist/server/lua/rules_guard.lua +132 -0
- package/dist/server/lua/rules_lib.lua +176 -0
- package/dist/server/lua/site_logger.lua +405 -0
- package/dist/server/lua/webhook_handler.lua +126 -0
- package/dist/server/migrations/0000_init.sql +666 -0
- package/dist/server/migrations/0001_brief_tomorrow_man.sql +47 -0
- package/dist/server/migrations/0002_funny_dazzler.sql +2 -0
- package/dist/server/migrations/0003_third_killer_shrike.sql +1 -0
- package/dist/server/migrations/0004_session_active_org_not_null.sql +10 -0
- package/dist/server/migrations/0005_warm_amazoness.sql +3 -0
- package/dist/server/migrations/0006_lumpy_joystick.sql +1 -0
- package/dist/server/migrations/0007_outgoing_maginty.sql +2 -0
- package/dist/server/migrations/0008_frozen_cutover.sql +3 -0
- package/dist/server/migrations/0009_credits_billing.sql +142 -0
- package/dist/server/migrations/0010_credit_grant_dedupe.sql +14 -0
- package/dist/server/migrations/0011_remove_credit_ledger.sql +23 -0
- package/dist/server/migrations/0012_smart_deploys_and_git_rollback.sql +81 -0
- package/dist/server/migrations/0013_fix_check_run_rollup_unique.sql +38 -0
- package/dist/server/migrations/0014_default_rollback_git.sql +9 -0
- package/dist/server/migrations/0015_billing_idempotency_grants.sql +41 -0
- package/dist/server/migrations/0016_github_auth_high_findings.sql +30 -0
- package/dist/server/migrations/0017_spooky_rumiko_fujikawa.sql +6 -0
- package/dist/server/migrations/0018_calm_shatterstar.sql +1 -0
- package/dist/server/migrations/0019_rename_workspace_install_to_prepare.sql +1 -0
- package/dist/server/migrations/0020_uneven_the_renegades.sql +13 -0
- package/dist/server/migrations/0021_shiny_firestar.sql +2 -0
- package/dist/server/migrations/0022_version_on_success_backfill.sql +6 -0
- package/dist/server/migrations/0023_productive_gorilla_man.sql +17 -0
- package/dist/server/migrations/0024_sleepy_peter_quill.sql +2 -0
- package/dist/server/migrations/0025_smooth_blue_shield.sql +1 -0
- package/dist/server/migrations/0026_mail_backup_source.sql +11 -0
- package/dist/server/migrations/0027_opposite_anthem.sql +6 -0
- package/dist/server/migrations/0028_stale_rogue.sql +18 -0
- package/dist/server/migrations/0029_bouncy_loners.sql +16 -0
- package/dist/server/migrations/0030_bright_darkstar.sql +6 -0
- package/dist/server/migrations/0031_conscious_catseye.sql +13 -0
- package/dist/server/migrations/0032_wet_mimic.sql +51 -0
- package/dist/server/migrations/0033_eminent_molly_hayes.sql +2 -0
- package/dist/server/migrations/0034_nervous_surge.sql +1 -0
- package/dist/server/migrations/0035_thankful_morgan_stark.sql +1 -0
- package/dist/server/migrations/0036_gorgeous_scalphunter.sql +17 -0
- package/dist/server/migrations/0037_clever_makkari.sql +28 -0
- package/dist/server/migrations/0038_young_magneto.sql +1 -0
- package/dist/server/migrations/0039_secret_lady_ursula.sql +15 -0
- package/dist/server/migrations/0040_sharp_red_wolf.sql +16 -0
- package/dist/server/migrations/0041_round_spyke.sql +4 -0
- package/dist/server/migrations/0042_dapper_havok.sql +1 -0
- package/dist/server/migrations/0043_fast_sharon_ventura.sql +15 -0
- package/dist/server/migrations/0044_amused_shape.sql +8 -0
- package/dist/server/migrations/0045_amusing_korath.sql +1 -0
- package/dist/server/migrations/0046_white_harrier.sql +35 -0
- package/dist/server/migrations/0047_sloppy_strong_guy.sql +2 -0
- package/dist/server/migrations/0048_funny_emma_frost.sql +2 -0
- package/dist/server/migrations/0049_nappy_piledriver.sql +3 -0
- package/dist/server/migrations/0050_add_service_deployment_image_digest.sql +1 -0
- package/dist/server/migrations/0051_add_update_status.sql +19 -0
- package/dist/server/migrations/0052_add_notice_target.sql +2 -0
- package/dist/server/migrations/0053_colossal_sandman.sql +14 -0
- package/dist/server/migrations/0054_add_instance_smtp.sql +5 -0
- package/dist/server/migrations/0055_rainy_klaw.sql +22 -0
- package/dist/server/migrations/0056_moaning_daredevil.sql +2 -0
- package/dist/server/migrations/0057_complete_prism.sql +18 -0
- package/dist/server/migrations/0058_add_server_is_local.sql +1 -0
- package/dist/server/migrations/0059_legal_fixer.sql +18 -0
- package/dist/server/migrations/0060_add_route_strategy_host_port.sql +3 -0
- package/dist/server/migrations/0061_notification_default_multi_kind.sql +3 -0
- package/dist/server/migrations/0062_add_incoming_webhook.sql +21 -0
- package/dist/server/migrations/0063_add_migration_logs.sql +1 -0
- package/dist/server/migrations/0064_add_migration_partial.sql +2 -0
- package/dist/server/migrations/0065_custom_app_template.sql +14 -0
- package/dist/server/migrations/0066_ensure_migration_partial_columns.sql +2 -0
- package/dist/server/migrations/0067_webhook_delivery.sql +28 -0
- package/dist/server/migrations/0068_add_migration_target_volumes.sql +1 -0
- package/dist/server/migrations/0069_add_project_composite_routes.sql +1 -0
- package/dist/server/migrations/0070_forward_git_to_server.sql +1 -0
- package/dist/server/migrations/0071_gh_device_token.sql +3 -0
- package/dist/server/migrations/0072_gh_device_token_method.sql +1 -0
- package/dist/server/migrations/0073_project_volumes_object_storage.sql +3 -0
- package/dist/server/migrations/0074_project_compose_path.sql +1 -0
- package/dist/server/migrations/0075_service_command_argv.sql +1 -0
- package/dist/server/migrations/0076_rollback_retention.sql +17 -0
- package/dist/server/migrations/0077_domain_redirect.sql +13 -0
- package/dist/server/migrations/0078_grant_source_scope.sql +30 -0
- package/dist/server/migrations/0079_project_readiness.sql +18 -0
- package/dist/server/migrations/0080_deployment_error_classification.sql +26 -0
- package/dist/server/migrations/0081_project_readiness_repair.sql +34 -0
- package/dist/server/migrations/0082_edge_target_verification.sql +39 -0
- package/dist/server/migrations/0083_service_incident.sql +84 -0
- package/dist/server/migrations/0084_analytics_daily_rollup.sql +72 -0
- package/dist/server/migrations/0085_resource_usage.sql +68 -0
- package/dist/server/migrations/0086_remote_infra_updates.sql +41 -0
- package/dist/server/migrations/0087_server_container_version_columns.sql +17 -0
- package/dist/server/migrations/0088_audit_source_and_settings.sql +27 -0
- package/dist/server/migrations/0089_auto_scan_infra.sql +7 -0
- package/dist/server/migrations/0090_project_collect_paths.sql +18 -0
- package/dist/server/migrations/0091_project_server_id.sql +26 -0
- package/dist/server/migrations/0092_project_internal_alias.sql +13 -0
- package/dist/server/migrations/0093_service_incident_org_index.sql +13 -0
- package/dist/server/migrations/0094_backup_restore_meta.sql +15 -0
- package/dist/server/migrations/0095_backup_restore_cancel.sql +19 -0
- package/dist/server/migrations/0096_backup_policy_retention_defaults.sql +20 -0
- package/dist/server/migrations/0097_update_status_upstream_only.sql +35 -0
- package/dist/server/migrations/0098_instance_product_mode.sql +12 -0
- package/dist/server/migrations/0099_mail_webmail_project.sql +24 -0
- package/dist/server/migrations/0100_server_ssh_private_key.sql +11 -0
- package/dist/server/migrations/0101_mail_resume_step.sql +12 -0
- package/dist/server/migrations/0102_project_workload_axes.sql +19 -0
- package/dist/server/migrations/0103_dns_credential.sql +41 -0
- package/dist/server/migrations/0104_instance_host_control.sql +15 -0
- package/dist/server/migrations/0105_mail_inbound_rules.sql +80 -0
- package/dist/server/migrations/0106_mcp_call_tracking.sql +23 -0
- package/dist/server/migrations/0107_build_minute_metering_indexes.sql +20 -0
- package/dist/server/migrations/0108_credential.sql +68 -0
- package/dist/server/migrations/0109_service_build_args.sql +9 -0
- package/dist/server/migrations/0110_service_deployment_host_ports.sql +18 -0
- package/dist/server/migrations/0111_host_port_claim.sql +381 -0
- package/dist/server/migrations/0112_backup_run_execution_lease.sql +12 -0
- package/dist/server/migrations/0113_docker_migration_execution_lease.sql +11 -0
- package/dist/server/migrations/0114_build_session_execution_lease_index.sql +6 -0
- package/dist/server/migrations/0115_orphan_target_key.sql +3 -0
- package/dist/server/migrations/0116_domain_ssl_challenge.sql +2 -0
- package/dist/server/migrations/0117_github_installation_workspace_scope.sql +24 -0
- package/dist/server/migrations/0118_git_sources.sql +66 -0
- package/dist/server/migrations/0119_backup_run_batch.sql +8 -0
- package/dist/server/migrations/0120_domain_primary_unique.sql +24 -0
- package/dist/server/migrations/0121_host_port_claim_overlap.sql +14 -0
- package/dist/server/migrations/0122_data_transfer_staging.sql +45 -0
- package/dist/server/migrations/0123_drop_cloud_infra.sql +7 -0
- package/dist/server/migrations/0124_mail_dmarc_report.sql +23 -0
- package/dist/server/migrations/0125_server_security_snapshot.sql +22 -0
- package/dist/server/migrations/meta/0000_snapshot.json +4848 -0
- package/dist/server/migrations/meta/0001_snapshot.json +4991 -0
- package/dist/server/migrations/meta/0002_snapshot.json +5013 -0
- package/dist/server/migrations/meta/0003_snapshot.json +5020 -0
- package/dist/server/migrations/meta/0004_snapshot.json +5020 -0
- package/dist/server/migrations/meta/0005_snapshot.json +5039 -0
- package/dist/server/migrations/meta/0006_snapshot.json +5045 -0
- package/dist/server/migrations/meta/0007_snapshot.json +5057 -0
- package/dist/server/migrations/meta/0016_snapshot.json +6053 -0
- package/dist/server/migrations/meta/0017_snapshot.json +6091 -0
- package/dist/server/migrations/meta/0018_snapshot.json +6098 -0
- package/dist/server/migrations/meta/0019_snapshot.json +6098 -0
- package/dist/server/migrations/meta/0020_snapshot.json +6201 -0
- package/dist/server/migrations/meta/0021_snapshot.json +6213 -0
- package/dist/server/migrations/meta/0022_snapshot.json +6213 -0
- package/dist/server/migrations/meta/0023_snapshot.json +6331 -0
- package/dist/server/migrations/meta/0024_snapshot.json +6343 -0
- package/dist/server/migrations/meta/0025_snapshot.json +6350 -0
- package/dist/server/migrations/meta/0026_snapshot.json +6440 -0
- package/dist/server/migrations/meta/0027_snapshot.json +6447 -0
- package/dist/server/migrations/meta/0028_snapshot.json +6581 -0
- package/dist/server/migrations/meta/0029_snapshot.json +6710 -0
- package/dist/server/migrations/meta/0030_snapshot.json +6748 -0
- package/dist/server/migrations/meta/0031_snapshot.json +6859 -0
- package/dist/server/migrations/meta/0032_snapshot.json +7222 -0
- package/dist/server/migrations/meta/0033_snapshot.json +7249 -0
- package/dist/server/migrations/meta/0034_snapshot.json +7255 -0
- package/dist/server/migrations/meta/0035_snapshot.json +7262 -0
- package/dist/server/migrations/meta/0036_snapshot.json +7392 -0
- package/dist/server/migrations/meta/0037_snapshot.json +7602 -0
- package/dist/server/migrations/meta/0038_snapshot.json +7609 -0
- package/dist/server/migrations/meta/0039_snapshot.json +7714 -0
- package/dist/server/migrations/meta/0040_snapshot.json +7820 -0
- package/dist/server/migrations/meta/0041_snapshot.json +7833 -0
- package/dist/server/migrations/meta/0042_snapshot.json +7839 -0
- package/dist/server/migrations/meta/0043_snapshot.json +7938 -0
- package/dist/server/migrations/meta/0044_snapshot.json +7982 -0
- package/dist/server/migrations/meta/0045_snapshot.json +7989 -0
- package/dist/server/migrations/meta/0046_snapshot.json +8263 -0
- package/dist/server/migrations/meta/0047_snapshot.json +8275 -0
- package/dist/server/migrations/meta/0048_snapshot.json +8289 -0
- package/dist/server/migrations/meta/0049_snapshot.json +8308 -0
- package/dist/server/migrations/meta/0050_snapshot.json +8314 -0
- package/dist/server/migrations/meta/0051_snapshot.json +8460 -0
- package/dist/server/migrations/meta/0052_snapshot.json +8472 -0
- package/dist/server/migrations/meta/0053_snapshot.json +8556 -0
- package/dist/server/migrations/meta/0054_snapshot.json +8586 -0
- package/dist/server/migrations/meta/0055_snapshot.json +8756 -0
- package/dist/server/migrations/meta/0056_snapshot.json +8777 -0
- package/dist/server/migrations/meta/0057_snapshot.json +8904 -0
- package/dist/server/migrations/meta/0058_snapshot.json +8911 -0
- package/dist/server/migrations/meta/0059_snapshot.json +9061 -0
- package/dist/server/migrations/meta/0060_snapshot.json +9081 -0
- package/dist/server/migrations/meta/_journal.json +888 -0
- package/dist/server/pglite/pglite.data +0 -0
- package/dist/server/pglite/pglite.wasm +0 -0
- package/dist/setup-JWKZIECM.js +25 -0
- package/dist/signin-3M3UPB6K.js +667 -0
- package/dist/ssh-client-NWQVRCV7.js +29 -0
- package/dist/sso-oidc-3VDIYOEJ.js +834 -0
- package/dist/sts-B3WIYLXR.js +1426 -0
- package/dist/up-P5DMQ5YO.js +55 -0
- 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)
|