diff --git a/.env.dev.sops b/.env.dev.sops index 893ba07..aa5e8eb 100644 --- a/.env.dev.sops +++ b/.env.dev.sops @@ -1,77 +1,76 @@ -#ENC[AES256_GCM,data:ErezNQ==,iv:GtNs5EgH0f1gI5cDTApW6Vvw2x90NI2rAGXBJpMmLgQ=,tag:EmNenJvfpuNP3HeYvD8UsA==,type:comment] -APP_NAME=ENC[AES256_GCM,data:+vZp0sqaYRWXFlI=,iv:kvBQt4XxEHI7E1v//c9Z/rqjvaJXTgx5cvHzHqzaI3c=,tag:EaJb6qZXvzzJEapM96j1+g==,type:str] -SECRET_KEY=ENC[AES256_GCM,data:UCK7JrrYd5oHSHdT4MyB+qg/L/Si2LrZxOEEkjJUV/s=,iv:tyZjyOe+7PBPa83jKMfSRrjCuKsqSk2StJLBteE91qc=,tag:Lmz32nb9Xv4RcY7akFjXLw==,type:str] -BASE_URL=ENC[AES256_GCM,data:T8gU8YF64yAH7umlYGh63Ju4nQOm,iv:woziZtqBwtv57Mohdc7QJwt5r7BGYkNq9yo6LxUus4Y=,tag:RVDFUzblyENvi6Uv7Agp5g==,type:str] -DEBUG=ENC[AES256_GCM,data:1Hv6qw==,iv:3Y8a1DCCaNgGsuaBVmUrjEqW1nUG+I8Dqi4XXgIWcNc=,tag:5Hw12JrPTc5T4xeV3CaEeg==,type:str] -#ENC[AES256_GCM,data:zATUJ7DT3TrnC+wYuxoo5H4Cnzvx+qtKBubK5JdlZIku71vaESrQ40LtzRf7bzE+nBWrlbLzoZA1hThR1xQUAF3YQFp6tMnECg==,iv:4d/TqqrUf/SMMfP65cRlHePm1E7uUiElXGAO1dVpQvs=,tag:oQVHdf+8uAPYo7qEBoAuvQ==,type:comment] -ADMIN_EMAILS=ENC[AES256_GCM,data:1fSFoO+nZ4PfdnoQfA==,iv:dyBsQa38WLH7YhhY03l+i4iaNmXXi/YauyT0CO/xtaY=,tag:otWDrofdNcFigotMcv96tg==,type:str] -#ENC[AES256_GCM,data:d8rqleReUHUt,iv:5f4bzvxWzty2PIkrAGb6iw+Nb2evkfPFFoH1F7kdFaY=,tag:GpqLD6QJcGGyEyUWbcPsQg==,type:comment] -DATABASE_PATH=ENC[AES256_GCM,data:m6BUOfuhyTcqZPI=,iv:t31+AEMO+Pa7jKjUPjVhe3DUe3yhY02xOF81tn7+Zkw=,tag:F8N72pVRqfJda6p5exHvjg==,type:str] -#ENC[AES256_GCM,data:zqiXDW4=,iv:wT9f8UaixLuj54yqtDX+SFRDCqhtCpuGeVWj6kXL9mc=,tag:eAdQVPZTURDT81ty9LjptQ==,type:comment] -MAGIC_LINK_EXPIRY_MINUTES=ENC[AES256_GCM,data:rzo=,iv:0zOl1jc3LwGxqF+UVHVAqBp1Sj7asyxc0RuG6OhTl5c=,tag:MhP/tAt2nzGyaY4haSNUwg==,type:str] -SESSION_LIFETIME_DAYS=ENC[AES256_GCM,data:CKI=,iv:RpK3WEPqZOQ9UgXNiH87DyD4mTRfJLUVt6Rka+rITQI=,tag:tXpGVO88TbovPWcGnogexA==,type:str] -#ENC[AES256_GCM,data:4F7//y4HtysenIogVqS6,iv:dzJEBKbprd9Sc5uKtpj7+eYxMhWHuqeRnfmJt3l61eM=,tag:SDOu+zmhdRwZVtV8Tjy0Lw==,type:comment] -#ENC[AES256_GCM,data:7jnSYJ2+MHYKp6H904+2BVSHBwhVqeJsLubNgud5vzC+pSG8/Aaq8l5Jne+73QbDYUJPDIW83yc4Elgd72SDh4zLeq7bSoY59mNM3Q==,iv:jDHERGIEAfCWAD6TrYrV6wM8dmlx2xxoItMA1PSv6mM=,tag:1jQjhbkr7dnTAfLGPDPPzg==,type:comment] +#ENC[AES256_GCM,data:9JNa0g==,iv:KnGl3/4KQWkVnFXn9iKU5z5Ys6KXWOnSEoE/Jjks2pw=,tag:ZD3nrOQmhUjPkZiwtV330g==,type:comment] +APP_NAME=ENC[AES256_GCM,data:Vic/MJYoxZo8JAI=,iv:n1SEGQaGeZtYMtLmDRFiljDBbNKFvCzZPNtaFBNauYY=,tag:Smsd20Ba56QZKVFpRmhRPQ==,type:str] +SECRET_KEY=ENC[AES256_GCM,data:a3Bhj3gSQaE3llRWBYzpjoFDhhhSsNee67jXJs7+qn4=,iv:yvrx78X5Ut4DBSlmBnIn09ESVc/tuDiwiV4njmjcvko=,tag:cbFUTAEpX+isQD9FCVllsw==,type:str] +BASE_URL=ENC[AES256_GCM,data:LcbPDZf9Pwcuv7RxN9xhNfa9Tufi,iv:cOdjW9nNe+BuDXh+dL4b5LFQL2mKBiKV0FaEsDGMAQc=,tag:3uAn3AIwsztIfGpkQLD5Fg==,type:str] +DEBUG=ENC[AES256_GCM,data:qrEGkA==,iv:bCyEDWiEzolHo4vabiyYTsqM0eUaBmNbXYYu4wCsaeE=,tag:80gnDNbdZHRWVEYtuA1M2Q==,type:str] +#ENC[AES256_GCM,data:YmlGAWpXxRCqam3oTWtGxHDXC+svEXI4HyUxrm/8OcKTuJsYPcL1WcnYqrP5Mf5lU5qPezEXUrrgZy8vjVW6qAbb0IA2PMM4Kg==,iv:dx6Dn99dJgjwyvUp8NAygXjRQ50yKYFeC73Oqt9WvmY=,tag:6JLF2ixSAv39VkKt6+cecQ==,type:comment] +ADMIN_EMAILS=ENC[AES256_GCM,data:hlG8b32WlD4ems3VKQ==,iv:wWO08dmX4oLhHulXg4HUG0PjRnFiX19RUTkTvjqIw5I=,tag:KMjXsBt7aE/KqlCfV+fdMg==,type:str] +#ENC[AES256_GCM,data:b2wQxnL8Q2Bp,iv:q8ep3yUPzCumpZpljoVL2jbcPdsI5c2piiZ0x5k10Mw=,tag:IbjkT0Mjgu9n+6FGiPVihg==,type:comment] +DATABASE_PATH=ENC[AES256_GCM,data:pEpMUrL7ZHAzMT4=,iv:eDGudDVsW5vF0sENri7gQrFlCEdoWYP6hT5ZeXXs3Zg=,tag:Gl91C6uRdCiJ7Jo1Z/MQsg==,type:str] +#ENC[AES256_GCM,data:xVzlko4=,iv:glHTshoRIkIaJNpn4onyAxPOtXTsNh/JohXJyyu4Ars=,tag:fQ/53HdxYXs2JTMx6O8rrA==,type:comment] +MAGIC_LINK_EXPIRY_MINUTES=ENC[AES256_GCM,data:Ua4=,iv:ou1kEn+fa42lZDsXaPvpodJcvAF+EZC9UIGNK/tBV/U=,tag:+ed8Bm/8pdIksH7O2X8WwQ==,type:str] +SESSION_LIFETIME_DAYS=ENC[AES256_GCM,data:/gk=,iv:kKWy+FaoLp8kAWpZzpoUHX8nVFRaA4yuTTVzN2TSYTs=,tag:QypZTVmTo4lXd7PKTWrBdA==,type:str] +#ENC[AES256_GCM,data:8HLEkeUESRt3bOxIQsma,iv:kzt8+SFNJw2r3LqwwQPzs9bCdacYSfHWPzIvTxARI4k=,tag:n+F13eILUiJCZ3NtQdo26g==,type:comment] +#ENC[AES256_GCM,data:cGkjKPIfdOPWoZFEXTAgw5lsu0LIcNhu1y3ab47kKbVEZMiCk+0KrEUNJcqbQ+ProBQ6F6N38PUhUl0lhKKjMqjepMZUUrUTqFp0Tw==,iv:WyFISLnRnSSOkra/p7bOs5BQWx+qFaaeeZM50EdrIgA=,tag:UM0EiRGy+RFXfdJqRuv3Jw==,type:comment] # -#ENC[AES256_GCM,data:P4C+IQhLXzs9SBKhIWgdKvXpMn/dnE0qeaMaQZsg3BYxNVFQYvJL46miAwvIV+eRNtdWZMXdYve6iOJ3fsx0H+hC2xl7HAOLZZrSZP/GUDs=,iv:9Em6JDy8MKSbX51g/5S7R2BdeRwtXh/8RkgNdIwNudw=,tag:hDJNBhfudzrDHWAQ8VwZAA==,type:comment] -#ENC[AES256_GCM,data:FYpzYV8avmJuvjRf07b9tDCAgwfE0AKBcG/Vd8NzKaq1W75mzE17N5MRY7xvwIrHEPpyvgm9lZfZAiWVhS6Nw2V8E/b/,iv:o680eJd30GT03Xnjcsm+P1bH+d6XKYCnpSkt1r9hRXk=,tag:VH1mGp19gxU7df7fLs3RTg==,type:comment] -#ENC[AES256_GCM,data:NSK0Ph74ABD9TD+TmJ+J1xKo59DDVGWno+I96XSsaBX9rKGLDkEI1SKyqsrwW9ReuZYy/5E=,iv:Y8QSzOMMiLVSuUUVCPbMaACzsHu1Cd8j8rqoAN99INg=,tag:Mixnrsfjq/Q+TOkGbWx1fw==,type:comment] -#ENC[AES256_GCM,data:ayv+LfzbDxOZF39CMYGWGR1r4LS5vmSO3p27Jm/p+9W+z73FmsYJeBY/k1sRYz5+RRFupJW/L18=,iv:DwtFFLL2zqUNxvEFg5tQEz0eq5quflqcDOCR6Y3IhVM=,tag:EpMqnrZUwkv4M+gw8YQu8w==,type:comment] -#ENC[AES256_GCM,data:9/K3pVWOl0cmXDg9VUcJjoX2RrFuLnqYGxK+hM9FXdUC4qjB9fZtaWNlYNIVqzNtjihHtpB2ex9Q5+9JCQI=,iv:iRt4qxShtLbe4dYBE2jY1KgeCEM4S3IrnqEpatkpUdk=,tag:DII9n35Qnx2n7qNlYvQuPg==,type:comment] -#ENC[AES256_GCM,data:pAL6rk14PeuF4kQs/dNret8ZtWgRsDfx5YBcEZtUPJSk7qHaDFFpZmG0Ok7mVRL1OIyoQXVOlIs=,iv:JgJ408wpCkET9xDv7yBOP+iN/bOg7huyc4xFcw6BWo8=,tag:1Esx3pxQBX6AHU3t7B58ug==,type:comment] -#ENC[AES256_GCM,data:VnpXVaOfq//CGtKnUtq6kLqm+qXPLQS8y30HaZ8YNbOMZEwUV21xvSC8B347148qHt1rHhGM2QFp/kKS8IxoLngMq9QWj3No,iv:v0eQpUn51pdGDSpg2A6WGX0plFYil0K4okICi0OJwGQ=,tag:BJnMZ3Vmx5Qw7MMLsFlsZg==,type:comment] +#ENC[AES256_GCM,data:icBc0Zv+oedobh8DOTwV2Fc+N0C9CqjZvLciC1dmEIygr/P5oOBLH9Bnhf7XW3X+fiLUtLPQPUIh9CjX+wVeX+MpUZj8ksS0meoz1O2kSBs=,iv:oWreqF6QxaFZn2r35uqY7yy/nItwy3k3VuXAcLyqMbI=,tag:ezQO+m6qkQSEwe17vYtYcw==,type:comment] +#ENC[AES256_GCM,data:BmlWl0f3aiOrEVglJisqHb507/ipmyRCUvkygs2jBfh2gw3BJgrCAAqoK+DekvIls2myRn7RynqWTMZKGXtJMHu5SEA8,iv:QjPoSzyNl6CBYmJAd2OfFEEoXO3jz0LL2VNegP0mY8Q=,tag:miTvj9PK1a00BKaAjUTICw==,type:comment] +#ENC[AES256_GCM,data:kzSYQCopgU9wXpw61WGfYpRtOjV3iEVvZ09RP4OqVl+Rqnd2wCKREKKrB7F15bp4BB3OzMo=,iv:TFFpROfYKaUlWQm3ISYkyYdZCarSJbqHItLMUplYiXc=,tag:Xbk3ii3DYE4yPe8cJOt+Tw==,type:comment] +#ENC[AES256_GCM,data:qbh0Mnu6wEbDaBideJQCZa74G/DqSWuoiy22zCGoKqKZ2YVQEf0QxRCO/DgOD8rdp1Neqp8u4oE=,iv:dKV76b8sT1ghlyEadeAqTjtNTXrBp9n5ZbGMGi2/GyU=,tag:1HsI5iDekTqxeYmcEpL3HA==,type:comment] +#ENC[AES256_GCM,data:kTLnLnwPVVDFKYncBbFjGmnbxmNfpPXSpKyZu5ZQx6PeVs5s6hpDa55zRXxAetyBAHsmV99ZV2q1NTDXF6Q=,iv:m/ulRFQcGl15vi2ohMwVeYBmcRtp274ROiKXPsyJkfQ=,tag:T0E5p4d/inHyuupbg7bZHQ==,type:comment] +#ENC[AES256_GCM,data:aziyEFGCGxbc+q2ma2QN4MvdhQ6bnYuZA7Cgqr6p3zGjPG3oybTWwILejJqD2lHmULXh0UN0qco=,iv:XZwwEUOAEXIUyXiiHFS/bdL91bWKIhZ5IzcXWXAR63Y=,tag:JLsDM4s2yh4aBFQtxWLhDA==,type:comment] +#ENC[AES256_GCM,data:JroBWIbSs5a7+lg/AtBNPxgbtxaztjmVzI1JXuhBmfWD45Qp+w8ePZg9PA1FYzPtEATPHso6m3tZdMTrPtv4Jj6ig/+KHOLn,iv:CbJp6MHOU4yk9OdynQTPwVgZj9Djw9IC9TE90go5RDk=,tag:NTVDg0mFeZsxfVeIFwFhPw==,type:comment] # -#ENC[AES256_GCM,data:k3uooGSCEetsRz85KBWdAo1k+DZMWbVc3V/PumOEhdfxHp2I0vRApdIfqimF7cJmW+uE5/aSK1zHMqc4EUgq8A5N3gO2LVmMkhA=,iv:0d1oROXKygJ3xnwQ+TZcjK02PpCEA3ydL6reRdAp6ZI=,tag:C8rWINR7+41JHxqmFGEyLQ==,type:comment] +#ENC[AES256_GCM,data:JDxXJ3IJVaBF+MOWe0WXBOPnee48RyjNDtflwVM2FFbLSp1h2uYf6+aRjC4w6pb/R1pl9+AzjjlQaTukjQVwXfbBVKH8SAbhOdU=,iv:K9kvgAbltLIcBo4vZ8NUiaL/Ik+x5Arl9Pj5sh3SIHo=,tag:IjneoyVD+dxd6N2PVV43ww==,type:comment] RESEND_API_KEY= -EMAIL_FROM=ENC[AES256_GCM,data:k4dZes/5yKevE9ywxItbe5s2fU0=,iv:NmGOq9a6/CfcEKzaGkz2knYuz8Eeluaf600kzYImYRg=,tag:oh8dvUP9MtuJFx75O5YOTw==,type:str] -LEADS_EMAIL=ENC[AES256_GCM,data:hlI9HMFloMmLEvm9deqz/eAR6ak=,iv:+ACxp0Q0uGB75BymlWP6to0f+njE5ybUTOIkxrNIn/E=,tag:XizFAGcWmGhbMMARt5PLxw==,type:str] +EMAIL_FROM=ENC[AES256_GCM,data:QX6duq5wx3z98o39nRXTrPpNXwI=,iv:ikpykHOeHRay+k3B4MvHn2SOuHNGOIuvjetOt+cjTrQ=,tag:8ryM56ogWySp9RAv0/ABTg==,type:str] +LEADS_EMAIL=ENC[AES256_GCM,data:aVFgeh0Yx7W/88noeURvf8rirv8=,iv:5KjsCMAsu1Ywz4BI2JjB1KmQ6QM94U1zlNGJ3BKl7Uo=,tag:voi0kjdhz0SsOQHqtMID4Q==,type:str] RESEND_WEBHOOK_SECRET= -#ENC[AES256_GCM,data:PyHRX/kgXwa24gyol3okcZ0KP29OS0T39/6bx7mbFQ7wfsnIDaUWja0/3F5tTDycvsy465M4Fe6onAQpKiyzf9CuNw==,iv:ZE3vSHGW+rD/dy2olWvTYBvQI/1SZImAAPW66HOns+I=,tag:r+LHnyBSX4PpH7OF21rPyA==,type:comment] -#ENC[AES256_GCM,data:J0vWvAfeSQlhthf5eWm8USRUAfkVfgIqmAnt3GsC8r692x8HLAX3NbZ9FN7pz5SgiofS/pNjqb/no0TINyUjsQL10jwRXPtPDzr3,iv:i3SYbUl2Xsi6Ua8Hgg9u2f4y9U9n3QrclRmiripgQ7c=,tag:D2cXlCE7wkHDUnPf5hNdgQ==,type:comment] -#ENC[AES256_GCM,data:sUC7z1bskxOjA8d9lWwxNfxelL22yYNuha/qbIUq9tT9XbiuDczKXNc2sNmP6QJdscSuXcX0dizk8qfmsAYwOm4IBc20JUmeYC6oPw==,iv:X4ghkHGgslkYSup814U86/mOiWljJBd7dc/4ZCdoyTo=,tag:z+cr+vaI3dENwJJivpnViw==,type:comment] -#ENC[AES256_GCM,data:bmLZN2890ZS2BMdXfFbPiou/RErEwEc0IHLhO2mVZyBljHxauIU36sIUdor0xIHsvNUH,iv:i4GIqVbNJ/NdX39AbzdB3JGs9ExvNxilguuvbzzkCVU=,tag:0D9vRXuno8P+LEWWg6S6CA==,type:comment] +#ENC[AES256_GCM,data:1HqXvAspvNIUNpCxJwge3mEsyO0Y/EWvD3vbLxkgGqIex0hABcupX/Nzk15u8iOY5JWvvEuAO414MNt6mFvnWBDpEw==,iv:N7gCzTNJAR/ljx5gGsX+ieZctya8vQbCIb3hw49OhXg=,tag:PJKNyzhrit5VgIXl+cNlbQ==,type:comment] +#ENC[AES256_GCM,data:do6DZ/1Osc5y4xseG8Q8bDX84JBHLzvmVbHiqxP7ChlicmzYBkZ85g43BuM7V0KInFTFgvaC8xmFic+2d37Holuf1ywdAjbLkRhg,iv:qrNmhPbmFDr2ynIF5EdOLZl3FI5f68WDrxuHMkAzuuU=,tag:761gYOlEdNM+e1//1MbCHg==,type:comment] +#ENC[AES256_GCM,data:dseLIQiUEU20xJqoq2dkFho9SnKyoyQ8pStjvfxwnj8v18/ua0TH/PDx/qwIp9z5kEIvbsz5ycJesFfKPhLA5juGcdCbi5zBmZRWYg==,iv:7JUmRnohJt0H5yoJXVD3IauuJkpPHDPyY02OWHWb9Nw=,tag:KcM6JGT01Aa1kTx+U30UKQ==,type:comment] +#ENC[AES256_GCM,data:GgXo4zkhJsxXEk8F5a/+wdbvBUGN00MUAutZYLDEqqN4T1rZu92fioOLx7MEoC0b8i61,iv:f1hUBoZpmnzXNcikf/anVNdRSHNwVmmjdIcba3eiRI4=,tag:uWpF40uuiXyWqKrYGyLVng==,type:comment] PADDLE_API_KEY= PADDLE_CLIENT_TOKEN= PADDLE_WEBHOOK_SECRET= PADDLE_NOTIFICATION_SETTING_ID= -PADDLE_ENVIRONMENT=ENC[AES256_GCM,data:QUmkNRMhtA==,iv:ynAcdJvkBhDC01fhXiPNzVF0rJ/06kbPfn1fJDyuwfE=,tag:JBaSLOK1pgrSd1RnouarlQ==,type:str] -#ENC[AES256_GCM,data:HdyY6Znpwyz45cIncl5UQ0q/COW9nTpDJtRpFIh/axiHSoJVb8xZMPMGZs+ZBlsdY2Fk6DLbk/QgQKou,iv:0dF92m4g6xOFl2tyI/XzN6iZ7Akjw0GmQoxtfi2UHAg=,tag:1FRSwRfSMjWHLdU+Z8nq8w==,type:comment] -UMAMI_API_URL=ENC[AES256_GCM,data:CpNizZHSgMxaXoI58ZqaHf8anQSPnlaScHqtYA==,iv:xcIXxNjo8/kPdB5RAlyHTvHfo51cEgxlKD0bum7JMOU=,tag:nFOLENvS9QnneD9X6IyE4Q==,type:str] +PADDLE_ENVIRONMENT=ENC[AES256_GCM,data:KIGNxEaodA==,iv:SRebaYRpVJR0LpfalBZJLTE8qBGwWZB/Fx3IokQF99Q=,tag:lcC56e4FjVkCiyaq41vxcQ==,type:str] +#ENC[AES256_GCM,data:2Hs7ds2ppeRqKB7EiAAbWqlainKdZ+eTYZSvPloirT4Hlsuf+zTwtJTA6RzHNCuK4em//jhOx8R2k80I,iv:1N6CNPqYWp3z8lm5e2Vp6OlpgHdMOiD7dsEYp23nMtA=,tag:ulWP/BFFoLljLMVCrsgizw==,type:comment] +UMAMI_API_URL=ENC[AES256_GCM,data:oX/m95YB+S2ziUKoxDhsDzMhGZfxppw+w603tQ==,iv:GAj7ccF6seiCfLAh2XIjUi13RpgNA3GONMtINcG+KMw=,tag:mUfRlvaEWrw2QWFydtnbNA==,type:str] UMAMI_API_TOKEN= -#ENC[AES256_GCM,data:aQSM5Lzgkz9NUUIugBA=,iv:2XKv8zYRiQJOObbPMZPF0KYjTKPfNwm3UDFuwhWhD3U=,tag:53tLhXhELygfPr4fpT+75A==,type:comment] -RATE_LIMIT_REQUESTS=ENC[AES256_GCM,data:rvuL,iv:yj7Jz/srP/mM5sq9ZVprXJonRz8yIYFObMEb9BH/gts=,tag:UPTPgf7o7XD6VRX8SM6Z3A==,type:str] -RATE_LIMIT_WINDOW=ENC[AES256_GCM,data:Ko0=,iv:HqyLf2FGYGvs39pHNAHVylRDmwGD7ZE5tzoVrJH5lWU=,tag:a6DUeRYQQgzo/onCCglk4w==,type:str] -#ENC[AES256_GCM,data:VjeIPAbAtb9UChZpMNB7JgylhPjJWf5TN2yirhXbvy0GwQz1ggfHOwMCfKT3gdQ0sS6538IVVrM7g7+t2LQU7UtwwXA/m/g7t7OOsMMLdus=,iv:h465KTCd4djaAYXNwTIoEYb0Jv+n++ggH1LSg9lGWsA=,tag:5kbWbQ/MKQnRD4dDaanVOw==,type:comment] +#ENC[AES256_GCM,data:HTG/nKNl9NMicZVt5nU=,iv:MfRqX6tzdl6SC61xjRxTrVRpTWGmmqslL/Vdy88Jtyo=,tag:NhOgm3+qJelmQaAAnITFKA==,type:comment] +RATE_LIMIT_REQUESTS=ENC[AES256_GCM,data:hy3B,iv:kouDI24Gac/S7aQMXRcl+emwE6/WU+F9egNhQ+MayrA=,tag:iZXV92kqnS0MppvW6Km5oQ==,type:str] +RATE_LIMIT_WINDOW=ENC[AES256_GCM,data:vE8=,iv:lS6cQX3VzHeVrlYHQTXYGgib1rYI9G4XoW/f5YSjVWs=,tag:3Bn8PIktDxD7HvUTHw6mnw==,type:str] +#ENC[AES256_GCM,data:KRlMK35PPFBTe7FOkbanuskbA4oFj51Fg290lRtwyHKoJxi7fHg7cueojwCiRSJestRguwV8g9UP4MC9bKzWssdFqvfdr7XEUuA3a+WWD9I=,iv:RZhJJS6tNZHecxn/862nnl8dg8OwsVYB/R0yPxYMXgw=,tag:dqXgcU8OSyJzOPJp+7Z+cA==,type:comment] LITESTREAM_R2_BUCKET= LITESTREAM_R2_ACCESS_KEY_ID= LITESTREAM_R2_SECRET_ACCESS_KEY= LITESTREAM_R2_ENDPOINT= -#ENC[AES256_GCM,data:m6v0sC7LAUHYcgslJ6U=,iv:M8WIwU9a+1DHgzHzUIbufU7f1M5EWCmMrND0ewn/4Zk=,tag:IvA37+vuLIhazIdyD9OKzA==,type:comment] -DUCKDB_PATH=ENC[AES256_GCM,data:f5cPVJ4hPitrUiGSADEbka7D80i9,iv:9w2lPLwCxGga0spvsvG5b8HAwb+UC3NiPMChhbPsTmI=,tag:D981doEKe/3QuJ5+8PefYA==,type:str] -SERVING_DUCKDB_PATH=ENC[AES256_GCM,data:jMbf6irWdBtvv2Fkt03IgrHf6HEg,iv:qlNdxAhNWAjlxN47CmSLsXS+edD/aCZajV2jMoyuvSw=,tag:AA/7hMNULpQYlP3aQ66MYQ==,type:str] -LANDING_DIR=ENC[AES256_GCM,data:0uX40uNxOg+wPToC,iv:ZegMiav+MbEQUhsxY55MH0OLJZpQl6eS3mekg1fyA28=,tag:EWiXChA2hts0y9R96FviFA==,type:str] -#ENC[AES256_GCM,data:iFa0hLjQSPP8Wn0mbEFVK8coTnFnI0gqnvGNC42sTJVy+b6DHL6It0mzIgO1mImRdsS0+QFPqRx8z990WtDg2QpRaguyY6R4xe0=,iv:Qe4g7sxTRJCWjC004lO1kNKuKnw+P+eU3mwU3PQ6kG4=,tag:U4T9Fwq44UXOdcKmvTcNYQ==,type:comment] -REPO_DIR=ENC[AES256_GCM,data:qw==,iv:gZKl9jg2mLHEPzAmLFhM+i3+hkEtA1UaKip7W1/fmaA=,tag:FBJSEpk70ws5rlYT/KvcLA==,type:str] -WORKFLOWS_PATH=ENC[AES256_GCM,data:2PhIF4TwbOC1VxJf5x412tfGAYhuicOWR/ArUY0dIg==,iv:CgCfmTRqat9eGKdCb7n2eYBq31SultpMx5EOo/5brGk=,tag:d7PTWh/kS1hI7WxXaZR1bQ==,type:str] +#ENC[AES256_GCM,data:4To0MRZIt3HxO7qjh4E=,iv:/caczOlTPECDF6mA1PKO8Xm4NeR1RZjgpt2Vuq+rfkQ=,tag:S/UGMqHZQX/Q20N+Ah30WQ==,type:comment] +DUCKDB_PATH=ENC[AES256_GCM,data:sql4dtOLeX1aY/kdaxAzCk47hm3t,iv:S63x40+5blcF8qYxMjqUhs2moukuy2yEQRPbUvXZSYo=,tag:lTLYjtyZNiv06o/hm6Grxg==,type:str] +SERVING_DUCKDB_PATH=ENC[AES256_GCM,data:xE05ajjqmYggI9oz4w1GBucUn0bI,iv:/C3D+iWSNk1XJ/xclTzdJTqOHR12Gwmo1xIxH/4nyL0=,tag:eNcgrb+QvL/y1jE8mb0DHg==,type:str] +LANDING_DIR=ENC[AES256_GCM,data:PNPOE7/MV/iQ24mf,iv:lg486nzb/vlOyTHVQ0HEO4fK18IEJNnuSc/CrQwUsHk=,tag:zecZp+Xfw+dL5GtUeIOg/A==,type:str] +#ENC[AES256_GCM,data:bsiiPYvTz0LtdIgopkPEtcgmtDzZU0W6uton/sqm++5UymV33B0m47LIpdH9xQurQtmoZwMCBkAe0FCqqz62D1dAIH1Q6lzzLqg=,iv:rr7aShvtJtAnBzcbr/O0wOONpDBzwbR/Wbx/YPPsKpM=,tag:YH1wdokUuudFvagnPuT8aA==,type:comment] +REPO_DIR=ENC[AES256_GCM,data:vg==,iv:TNMZ6lrajWy6C9q89/AbRkBawBc2YaGsn2elbO8V2Wk=,tag:va77fkt8VDpPG8pZu490uw==,type:str] +WORKFLOWS_PATH=ENC[AES256_GCM,data:PehxEUMb1K3F1557BY3IqKD7sbJcoaIjnQvboBRJ1g==,iv:WfniguOksC3onCSyDlBpfKC8bE9DAt7evoeYX0K0lvE=,tag:sdRWDqkk9dtuESvfbRBfCQ==,type:str] ALERT_WEBHOOK_URL= NTFY_TOKEN= -#ENC[AES256_GCM,data:uuuY0NpFnt4Ihe8=,iv:qhOHBKuz3RlzcFWESucb7LxN54T2QKS/hQ7p+PwQbK8=,tag:Q9AGw0s6t7nrtoFbZKbQxQ==,type:comment] -PROXY_URLS=ENC[AES256_GCM,data:tioCCvPFz4kZDY+KWz9VVpF4GFlWizq62opU0UWWRWJpbuMntNxyITxxp/VSLt+nMKBgL2RsD69qeCjWIktO2B4Ys5qEPznPTjDQx0LkSqFHjeMifFH+4yLh7JJtNMm9es3txYFj63QaT3LOnh/yXoM+VeSKl8BlW8n+H0yVf+0WWD4lkTDeY4wag4AjxL12/0M5FRsSyLG9tUYiPE/K6dL0eqprZ2Zwp367lTIcnF9mVXE9lRJXm8/wKe479JED/Vbcndp1TKZc9C6T8SvlnY9JQSxmhUOr9JMSofXug7OrBr8OEIFcjt10LfUJabsV462BuuCxsG9rX9Jjxytn83oIcTMIL2A2W2e8pLY2w7AND03q/KVzU7tGa55QVdMsNB/ZC6lwwZQpEHxpfr1g/62NliBtsOdD/XSzOmJDOghnYbnDuEp6C/E/IyFDcdOL8SnUv2uVzkyZZGIOs2eSaXT5TxKO7qWpMWwELAK+EGaB9aBT1YvD9nLP/DJ9JlGP8O76pwu+v7cR/upz3la4OvU5LC8WGzi6e7Fh1TlFVVWIzq6wjnD/Rai6L9gzhixCriNDbmZr9YLl2XeLNHZ8cCROm9nWxMBTAjoIk0efzEmcpY0JY6V/uAQbfmJ+YWo=,iv:Rm5NFF4XILpH+GU37PQwWvNJbQnK+Q9lS9LFtCSgMZc=,tag:DIcae33nOPI/6gag8VxJ8w==,type:str] -EXTRACT_WORKERS=ENC[AES256_GCM,data:Pg==,iv:TtnwhOjQuffZ1j/jqPcKWaKZKPWuuTN+pTgohUieFVA=,tag:O+cLuOWVi6SPlpOAgk+ZYg==,type:str] -RECHECK_WINDOW_MINUTES=ENC[AES256_GCM,data:v8c=,iv:1OLd9YKt7usSJjfMEv/nQVBdn1Yomw4v7IK6Tg1PBTM=,tag:26UlNnOpCbzjrM/9d2QWbw==,type:str] +#ENC[AES256_GCM,data:BCyQYjRnTx8yW9A=,iv:4OPCP+xzRLUJrpoFewVnbZRKnZH4sAbV76SM//2k5wU=,tag:HxwEp7VFVZUN/VjPiL/+Vw==,type:comment] +PROXY_URLS= +RECHECK_WINDOW_MINUTES=ENC[AES256_GCM,data:YWM=,iv:iY5+uMazLAFdwyLT7Gr7MaF1QHBIgHuoi6nF2VbSsOA=,tag:dc6AmuJdTQ55gVe16uzs6A==,type:str] PROXY_URLS_FALLBACK= CIRCUIT_BREAKER_THRESHOLD= -#ENC[AES256_GCM,data:pA90yPzfo293IovEhFv6+z/EOj2xuNPkOOx0u2Im9b5tPrrGks8hNbmcJabL6qOQe5+YwIccdSsTscJ+DocigNrIZcMY/+0h,iv:3+tiv1qAVX+Ex4Y49qTj094B8nxqhPEshTYhimlC1xE=,tag:gxcDM6Yyb6pUPO15NkqxCQ==,type:comment] +#ENC[AES256_GCM,data:ZcX/OEbrMfKizIQYq3CYGnvzeTEX7KsmQaz2+Jj1rG5tbTy2aljQBIEkjtiwuo8NsNAD+FhIGRGVfBmKe1CAKME1MuiCbgSG,iv:4BSkeD3jZFawP09qECcqyuiWcDnCNSgbIjBATYhazq4=,tag:Ep1d2Uk700MOlWcLWaQ/ig==,type:comment] GSC_SERVICE_ACCOUNT_PATH= GSC_SITE_URL= BING_WEBMASTER_API_KEY= BING_SITE_URL= -#ENC[AES256_GCM,data:ObwV9+nxI3OjibZaWomt41y0b+bdHgcdH3r3xddgMyU=,iv:LwdgMBeoUid9GGRwmn/HG8HvpdDkwFnH6eV5Tr2w3hs=,tag:MxfkT6hQ2hptiim1VKtNaw==,type:comment] -GEONAMES_USERNAME=ENC[AES256_GCM,data:62LndEyH65LKGXI=,iv:BBm1OOxgJa2fkuCyneTgcHg3ArYfkA35NOOd2yT9MW8=,tag:MzRGfuDOnA9pbrvXerOWRA==,type:str] +#ENC[AES256_GCM,data:ECsuDMQipS6YmFpSm1vqCsR2fUW2zN1Mg9VcUlw0roM=,iv:j+F6Akx2bklGMkFTux230YcZjMibA+Qp+qvgkGXl4Jw=,tag:7aO0wbmP/qB73wLgtiSJ2w==,type:comment] +GEONAMES_USERNAME=ENC[AES256_GCM,data:aSkVdLNrhiF6tlg=,iv:eemFGwDIv3EG/P3lVHGZj96MieIsr85e4xYmEIpZyfM=,tag:McpZMNOIO3FDkSebae2gOQ==,type:str] CENSUS_API_KEY= -sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrOW1qbkdDUlZobGRqbDFM\nS0NRTmdSazY5S08yOURBZ1lWeDBuNi95aTI4ClVRTWlnWXplWjVuNVE2dmRzMk1p\nRk5ZZHlpT2RVcjJqb2FpTEdOZXlKbTgKLS0tIHN6UU1tazd0cmNscVUzTmcwNE5L\nNE5uSEtiY3hwSzE1bm90b1VNZzYwK28KJlCJWkMx7dMoBtJi252GwERp6kSkuVFu\nJBwnSJ2KC5/zzjcfnLprgc/b3s4RsbLYDTkcPcuF08X9F3R8uqScdQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxNWNmUzVNUGdWRnE0ZFpF\nM0JQZWZ3UDdEVzlwTmIxakxOZXBkT2x2ZlNrClRtV2M3S2daSGxUZmFDSWQ2Nmh4\neU51QndFcUxlSE00RFovOVJTcDZmUUUKLS0tIDcvL3hRMDRoMWZZSXljNzA3WG5o\nMWFic21MV0krMzlIaldBTVU0ZDdlTE0K7euGQtA+9lHNws+x7TMCArZamm9att96\nL8cXoUDWe5fNI5+M1bXReqVfNwPTwZsV6j/+ZtYKybklIzWz02Ex4A==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_0__map_recipient=age1f5002gj4s78jju45jd28kuejtcfhn5cdujz885fl7z2p9ym68pnsgky87a -sops_lastmodified=2026-02-24T20:32:28Z -sops_mac=ENC[AES256_GCM,data:hQspZvYHHTsf1vFtNUvhiyRSkfynxNRUjGCjdK9GPGC30aXIzhg2si/M8wmk3VyhTMOuSFVtNgiiefvChO0N4hwqDkp2llEnkwy/uvtnCTLKNF8I90GS4ZOXNdYN5bTAS/0EF3gWOTPKl+EhJQ4lAvb3k+PY4fI7bZIkWf7sSbo=,iv:JpsFyMTpI0lfncfzPvt0snAqyNfCGCrSiQinIuDivUM=,tag:DBAIePZdHq3lfTn3AycBgQ==,type:str] +sops_lastmodified=2026-02-24T21:27:19Z +sops_mac=ENC[AES256_GCM,data:KuL4wOGAEnMeXEDUKH7MXPhRFln4jTMKJAikTmkyYYxlFsxbTy3o+i5wwpfEZ7oqq/76v7XE2rhg9KMMLfnbZ2rLH9I/6kJRDtlZUUBCdKI6FCRnFbsgmzhuoXMHuFrj4B054u/C8QN2YwL7Mke+Gs9fglxvBrmhN58JAIOaxew=,iv:qu7rdFffw8IBHRP9a1tpPlRexg0b2f6lcpLu9AVbl5k=,tag:h7NbJ4bl/B8/CGVM/iW1Uw==,type:str] sops_unencrypted_suffix=_unencrypted sops_version=3.12.1 diff --git a/.env.prod.sops b/.env.prod.sops index 384f87c..b4bf3c4 100644 --- a/.env.prod.sops +++ b/.env.prod.sops @@ -1,64 +1,61 @@ -#ENC[AES256_GCM,data:Wav/NA==,iv:aL+BhiA8jZhSxEbxDAYuC6t/AOJhTmTL1e4CxZKMFWA=,tag:dm1w5IiHOqWU5rGshI+Lfg==,type:comment] -APP_NAME=ENC[AES256_GCM,data:POhoUbLUGvXG9b4=,iv:+O4EWtOIvkENCYVyKR2i43MzAfcN/qYmuKLzAW5cBMs=,tag:9uVonTXvK9is4wysYgx83Q==,type:str] -SECRET_KEY=ENC[AES256_GCM,data:Sc9tp5z2+m3k,iv:T68rsh79CCuWd4UPzoDhNmxt6PcDF3wYcUC1/kzJFgU=,tag:Qk/NEsVc1ozZC+gDFbw7VA==,type:str] -BASE_URL=ENC[AES256_GCM,data:CVWA4DYxDj8ALfxeFjryj/15y8sqsA==,iv:Q90DnZyg5lnyClvoRtkANc7Pw7Kf0b/IjGCI6X0EC34=,tag:UUnnGJQWhtXMsxo/9WtoPg==,type:str] -DEBUG=ENC[AES256_GCM,data:gp1pqrY=,iv:un/NxyBzL2bsVcYfw5p6YEFDS7PQ58tsgzd/edjKxvw=,tag:+/2hHRk4ePIQWewrAwQwzQ==,type:str] -#ENC[AES256_GCM,data:IBz3HceUcQ2ousTJJw==,iv:d2VdDCd3YM35u2wLtJi/wAwIGD4gwAyL6lYEaGAjREQ=,tag:+9tArSTm/sZhOW23hT/XmQ==,type:comment] -ADMIN_EMAILS=ENC[AES256_GCM,data:IcQNF/E1F557,iv:BZS+H2aujJk/iJ8KNphdKfbNfEPTCCF/YMS+XylA4LY=,tag:jVNngz8DBmY/y0wyI9upPQ==,type:str] -#ENC[AES256_GCM,data:Mh0RRWSB8xzw,iv:NP2x+wqF64GZz9/VauC0CX+0yJHntHlS5TLMTH3jIIU=,tag:2BIIEevah5ZXmQb1MdH24g==,type:comment] -DATABASE_PATH=ENC[AES256_GCM,data:m0MbpPzsUpsNTl4=,iv:W2kV+ZFFjs2mrifZALMLtvYcFsDpEuJ9302zNMEeLQA=,tag:NJEe/EZeisS/dRZE0xsdSA==,type:str] -#ENC[AES256_GCM,data:cab2fnw=,iv:0O2xG1Kd72eBOzuPsENcEN2yglgGketYJ4x6eoBlBFg=,tag:e7UDXywCT8I8i+HoJTDnXQ==,type:comment] -MAGIC_LINK_EXPIRY_MINUTES=ENC[AES256_GCM,data:7bU=,iv:Vov5Eqkg17sOr0dMuKH2Yu7GEh+NrA5eQisR1S6l0x0=,tag:Dzv7DynR9bw2vGUYBLyTLw==,type:str] -SESSION_LIFETIME_DAYS=ENC[AES256_GCM,data:vUE=,iv:NOGW2PmabHJ4Jg4qqGAthVJm215oP6ZXDxotmGcnaxU=,tag:6HUzPr5vC/H4xTjYDBsZeg==,type:str] -#ENC[AES256_GCM,data:hkqR+T2LqHF0P6Rk9ct0,iv:muiERgZJgUzNCkraL6fpJW3Juq57UwPkcHYBoqHzPZo=,tag:hL79NkHIjiK2APU4u8TYgA==,type:comment] -RESEND_API_KEY=ENC[AES256_GCM,data:c22My3N0oeaD,iv:5HN/Ya+UdlEH47wF5Bt0yrm+QSWICSVnjllAJbICgbk=,tag:blThzzKKFgSCAIhFjMPiwg==,type:str] -EMAIL_FROM=ENC[AES256_GCM,data:/xYHYA1ldi1vbp+SB4OpY6yiLf81aBsKk3I81jWZUYG23w==,iv:C3bvthAepEZuceWKRPxijtxbUPI+REmByVgMDwTt56s=,tag:Ue9dOmZmcwk1LJ6C5/38Jg==,type:str] -LEADS_EMAIL=ENC[AES256_GCM,data:OBxQ0O2S0pz+37t6QfGkeyfEeP4Uj6iiFRl44JTIPW6Ptg==,iv:Tat5MDknzUQxx9aRsiOdOYfZhFSyMeiK8GPdSaBLCKo=,tag:zeDlnndu2ltPUGw3cLxSnQ==,type:str] -RESEND_WEBHOOK_SECRET=ENC[AES256_GCM,data:IkwtdB8OVNoc,iv:HtDFBAeysVW+lF40+Q41OkuKeT+IqMj5QjN3WiWJSUs=,tag:ag8SkdpBJ2kgMQgQjf9ytA==,type:str] -#ENC[AES256_GCM,data:YYm/fBT+tQ==,iv:SfHfEjAUDcx43txowHIOYW8lbESf1aA3G3kfduvwWCc=,tag:kU30Pw6OpOxkwJnDLP3rMw==,type:comment] -PADDLE_API_KEY=ENC[AES256_GCM,data:TiTjORwV6MOp,iv:Jl6P4e9M9EHhzAPUqZp2vP9Y5jk4Qk8lBf+ZhR7iUOQ=,tag:FeJ1xhBScZz5mbU80c2c0Q==,type:str] -PADDLE_CLIENT_TOKEN=ENC[AES256_GCM,data:zuOECQEwof2g,iv:2wywplC0WoFjeahjBhItLCQJuzZjPnc30eiBt+dKPIw=,tag:8e3lQt4/VRJLCIIrDtBKtQ==,type:str] -PADDLE_WEBHOOK_SECRET=ENC[AES256_GCM,data:51gkXZ/DqOZf,iv:r4hTwBeLvq2gH+ctltQUe2ErCxaQtiMjZ6+W/Bh6A3M=,tag:CY/X8ZgVgj7W7MAANz5jtw==,type:str] -PADDLE_NOTIFICATION_SETTING_ID=ENC[AES256_GCM,data:F/A9e6nLY0FE,iv:wt0hB0vIf6WR+Hxp5lTjabIBpcIyRqE7S/h8NW6RT48=,tag:CiUqrb5vH9XiiJ3Di+DEQQ==,type:str] -PADDLE_ENVIRONMENT=ENC[AES256_GCM,data:oyN5vq6WU9+GPQ==,iv:5Vp6AawTjCaet/Vw3ElfOiQs6m78QC46+rMCfA0KCZk=,tag:TRQxuYr6gs6EMPWnI7k5mQ==,type:str] -#ENC[AES256_GCM,data:ETizP+U6,iv:bzs+KIw54HXbCdQncCJJ9NAUiMo4xdE8nAxHxZtl5HM=,tag:5Hq5QuWQxqlfC1gISfVs9w==,type:comment] -UMAMI_API_URL=ENC[AES256_GCM,data:3I67uzhmagf8RPwRD7HiXLCnCjG10em98fVsuA==,iv:qACYc8jKe/816rql5COoUL351e58qCzVi1cdGA5JfZ0=,tag:VwmpCJittpYaN5S6grCKMw==,type:str] -UMAMI_API_TOKEN=ENC[AES256_GCM,data:MXlJOXEh45OT,iv:9nsj2IXpc/EHEaPrj8RkKZC5aH+ky2x6geeWoWuPtJM=,tag:nuTxbjhUenue2krhB5ZNuQ==,type:str] -#ENC[AES256_GCM,data:I3LUWXInlHVEt5HfDpY=,iv:OVGbuQUc1wB3phmwAONAx0DDudF7EReZ/JFwjrNTHU0=,tag:S7nu/WpB5wKU7K7LLwCgAQ==,type:comment] -RATE_LIMIT_REQUESTS=ENC[AES256_GCM,data:1mFf,iv:YFckwytvkxgrLKGGOpKyZKE61t3QpkxLAbXR2Th03R0=,tag:zo3vU1BlwcE778jcf2ugBw==,type:str] -RATE_LIMIT_WINDOW=ENC[AES256_GCM,data:dQ0=,iv:hD+YjNfATvzmVc/cpHs4z/601ezfoxRuXkllqShcio0=,tag:Zfw/fZoEmxat9WjPtx+qQw==,type:str] -#ENC[AES256_GCM,data:ZWwLbf5Y3Y0OobFHq45HGcGn6MsG,iv:Y/H/ZafbT+2YArnBe/mM1KkHFuuUDQsAf3JQgonIDUU=,tag:QW0MvN4e3mDsXGHdkaAF7Q==,type:comment] -LITESTREAM_R2_BUCKET=ENC[AES256_GCM,data:avxiVQAIebLG,iv:2CZ1MGjGScdgNgm0nVexurhJFC6UQEmi2ldVR/RxBr4=,tag:oZT+tGt+/EGQhpH8jiwHmw==,type:str] -LITESTREAM_R2_ACCESS_KEY_ID=ENC[AES256_GCM,data:7Mn+GggjzjbN,iv:vmUfPamn8pJR/vFtnO2rhD2+ALHUiZ8ahPTGzncsyVM=,tag:v1Tp97ZsiOJocqDoXCb58g==,type:str] -LITESTREAM_R2_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:CN+aCZHaIANh,iv:X34JZ/OC5AalWZVAlDoN0G1Kch2z1Q3Uo18PJa1NTE8=,tag:wAqiEz6UCHcaISCOzOgtgg==,type:str] -LITESTREAM_R2_ENDPOINT=ENC[AES256_GCM,data:C8UWFesY4/M+,iv:6ti84Kr0aDxs1S7Y17uNb78j2EzjovAki6N6cAm7agY=,tag:R9Q2Ug+HXpXYm/gTF/be1g==,type:str] -#ENC[AES256_GCM,data:qq7NU6KOZT/mwuTunBs=,iv:5R65DWMFkHdoeKsBiuDQFJqLdoH+nwTsU/DbWD+4yWM=,tag:Daf2m6DEi5pAJ+NQSXFD+g==,type:comment] -DUCKDB_PATH=ENC[AES256_GCM,data:nvlaHUuTDDHl2vx3CMVCRXra6QuJ,iv:2M/eptAN/wNZgZTC0kye4dF0rWUGkjdK+hwAOuyHU1s=,tag:9rExFDqVe2e9KWCTdgnQjw==,type:str] -SERVING_DUCKDB_PATH=ENC[AES256_GCM,data:ZdbRf/E5WVf9CfJ/mA7CM/C80RRW,iv:0dG5+oloGs8tOno4Lu8gJFVQ/ilTQVhqiZn7jis8u1E=,tag:kmphDZt1Y/D8qfbLJQCLwg==,type:str] -LANDING_DIR=ENC[AES256_GCM,data:NQQbZyOeMGCMxQOo,iv:E7+B45xAqG71yt4gc4aa3YA1KTJWDD7OcY9SieMDNd4=,tag:c2S1aDMD8zDQfmFew5sM/g==,type:str] -#ENC[AES256_GCM,data:BASJNxsOqfahZJ4=,iv:Fxwpluhe/V1xVMqO4rWwRamKEIHGfUrGMsx1XmKZv+4=,tag:2MthdZr4YMkVREbSydQC9g==,type:comment] -REPO_DIR=ENC[AES256_GCM,data:FmiIf4/s9qcF1KFriNfZIg==,iv:ur2jpVrM0rHYT55Ir3Wvb7InWr7J3Bxdt3s/D8w222w=,tag:Yk+BUcS2Ae2KzJzLQklcFA==,type:str] -WORKFLOWS_PATH=ENC[AES256_GCM,data:JAKGfDMe0wptVhisI7YC9GMBdslurVuaSG8L9aXI7A==,iv:s9P3m14zWUs3REQvypfQ3j/n/Klt4hPzwGqbGDOEOR8=,tag:wTDLzQ/KUjde7zDpG7rYiQ==,type:str] -ALERT_WEBHOOK_URL=ENC[AES256_GCM,data:3roAiuwKnSNzSjU9d5wIPRJAW7e9l7vtxc7Lj5Sf9Uo=,iv:vC738fioTDsWrVB///9GkKV7Jc67hlvuvcfnSAzwpuQ=,tag:v29o37UGSoA6/S7VxwENaQ==,type:str] -NTFY_TOKEN=ENC[AES256_GCM,data:hQerDktsRYZaBaunhS0koSVtehopZ1Cp/p+9v/FbIZU=,iv:b204WBPs3GYfgfCWr1mi58QN+iLO2Ekl0jrRQWcKoAY=,tag:+7h3on0TNiJUqg7EREZRYw==,type:str] -SUPERVISOR_GIT_PULL=ENC[AES256_GCM,data:AQ==,iv:/gJIcFRMd2jU97paHwXKzREQBOeJ2vq9l5ux9gDewO0=,tag:cMa4+fgk4oqUqc+DfBkSSQ==,type:str] -#ENC[AES256_GCM,data:u1BQCX8MCMcj1zU=,iv:Dtk4K4eZ50w3zbZKlZxrvytsWlYFteXFv+0QBYJJ59U=,tag:8iveGLmKeOrVk2bMVG7D7w==,type:comment] -PROXY_URLS=ENC[AES256_GCM,data:KH3171qwQp4rkbP1gJXYUqicmZuS5q+7xQ3Hv/Pb2MG1Sp/RbQwpEMR7X2ZiQYO6WUaIcZW/87qAEAShFe/w+qPHACSi//f7zwxv5vHNAhBaxMeU4tPudh9XGAvfyKonNdrU1rrPWIyQPVZ4ZKOUWJsetOy6P1Xly3PLpk8xh4jfLUmKuHEbuQcJInRqhcJdjSecLcAvaDmElZCDhr6dhq2PVQdciS/0LkDLj1xkZ09UrfKbS0fQD8//SM7VX+mPUNjIVK+4kytTbccrK3w79HEgFUnWa4q5PDnzYUFzexKu8pJEQwEl5gkBoHZo8lDoNNP2qed/pdEE9aMxNOSDJsoidW/vIhSxk1GmYwLuzLXAMcLdvujjrgLrt1lvZEyuWwGyRDhsYSp6SA80U48B3UrgFK3xXzwf1YuZHpcCN3Zjb82KLgXJIUzK+LA9kDiGMdzyEtNT1q6+RdCHS7I4XXgcFPaTJL6J/avUqwU31YpvSMM0LItJeKM9I5FL5BbC/IDhE1ax26Px27Hos710+lI3eUu2RViw82d650gqSu2IFfGbGv9ECsTOyUR8no427iqyB/003DAkgxjbSCs6oJIayKHJNULWAMks2mx35gGQathVLdmysfSfLYizP9I=,iv:IZTwqbh76CRXYPVYyZ6PlSjKof+dE1XHvomV8nA6xWM=,tag:de07uKNUmsGOX/2trFqWfw==,type:str] -EXTRACT_WORKERS=ENC[AES256_GCM,data:nQ==,iv:rUlSbl5+wdpY9OATciWw7u/Wniyi+ctJDyQ2jzdLI1U=,tag:NaVN4gJo2M5dK0Hq9/H67w==,type:str] -RECHECK_WINDOW_MINUTES=ENC[AES256_GCM,data:kFU=,iv:2yLZn5eC8UXCfOPNa+1fOnRnaY3JhMlh9AdBRAyrCZU=,tag:Dalqeod45g0mn4Mn1YEMvQ==,type:str] -#ENC[AES256_GCM,data:GK/IYt5yyMbVLGx/P8Hzyfc=,iv:mzIaCOzunFypa+eJhf/gAWxZDixH6bxauOZjo6ARVEQ=,tag:r1650Kgs7frUDAVJqpQ3Yw==,type:comment] -GSC_SERVICE_ACCOUNT_PATH=ENC[AES256_GCM,data:ZgsW82cm38rR,iv:Qz4oSAKQ2U3dBgy1GtDl56GGzCqnmvldA9ByiQLjH94=,tag:qxlF/5CvhNd4fOPajwrPvg==,type:str] -GSC_SITE_URL=ENC[AES256_GCM,data:nz5OC1nciCmds6bl257oo9IX3SA7kA==,iv:+LV0kDG4xfwL3E3oGEsUB1h3W7ZpRijv3LipWq+GHg8=,tag:zUmIKWfHd6KwD+cMZb0ZKw==,type:str] -BING_WEBMASTER_API_KEY=ENC[AES256_GCM,data:3h4h5BIApJdX,iv:lG/eNi0HwBF+FL5gyhDUiqHrLdMe+s8/PbXNqInR/i8=,tag:dvXNTf/Gn0RNE+A0U2fzPQ==,type:str] -BING_SITE_URL=ENC[AES256_GCM,data:ij5VYDk61wpIF21DdG1B6oEwN4yzUg==,iv:gJWQIYMCpjdVV+BoWxlpYy5uyM4M9WJQuHgud4Z7djY=,tag:rQwlCHjLSiLFjrATleiHVQ==,type:str] -#ENC[AES256_GCM,data:CJ83yX864ts1yvmP7pQXIbDqXbmwu6o9tAm4AMBH0A4=,iv:GgOY+GkLnS+mMu/iQSe4EFyMjC/tGnSR25GvvYS/gdA=,tag:sHqmi1Z+PMflJo/rHbTdUA==,type:comment] -GEONAMES_USERNAME=ENC[AES256_GCM,data:d9jN+L/93b2wDrY=,iv:IsmVx459v4vw/XREahd3SN88U8yCKQwaVaTty7b2big=,tag:OgO3lzdCt/Yg0S2Z/t9Row==,type:str] -CENSUS_API_KEY=ENC[AES256_GCM,data:KmceIQqzlvJ3l7yPCksP1ecT0KFqo5/owrRx34/u3Md4WUTj2RGn3g==,iv:1YVk6/OUZNxXOkzZ5qZXj6RNhy4mfEnus+uBnRcRLKE=,tag:rP1y2XEvQwim1rq/GjMPNw==,type:str] -PROXY_URLS_FALLBACK=ENC[AES256_GCM,data:hB9OdyCSGXVz6U1wPuSElzW/2rzHinqPBFFiJv9ugiXYlwj1uviGe0SHZ68AQHB050s/1ku3jDnzPwdM,iv:d1e9RLg5LLCPGKeZS1lkmh44Tt9GEW+ZC2YBMU5rnxA=,tag:whOwZYndrq7VB8flwLmW3w==,type:str] -CIRCUIT_BREAKER_THRESHOLD=ENC[AES256_GCM,data:kF8=,iv:6d/79VZFtIQtxKcvtVKF5/69KIBr7bIWpTPKt+vt6fU=,tag:yQStmU9Gjhs5CJw/r9b9rA==,type:str] -sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhRU1MWm44bGpuSlRyVUpT\nU0syT2x5eU9lSnR6Nk9DejFNOUJYaFUyeENvClJpU0d6dTR6emoyRDIvaUhnTDNU\nL0hpc0w5QWI0K1hKa1p3NTVCU3Jyd0kKLS0tIDBOUkdjNTI1a0ZLMzFpcjV2QmZS\nNHltZ2hIcDFRVkVmSEQ1bnFKdk9YdW8KqJSaCtueN7XdOBfgzOfJYDHUga2rgWXF\n5XbTf4WE8HMqtWWgAq6ZQLFGSbJAhKmPlscjyp/VflFZvKvVqm3qbw==\n-----END AGE ENCRYPTED FILE-----\n +#ENC[AES256_GCM,data:8qKvOA==,iv:Xci2F8lcBpT7dmhzaDe6sfrtQi+yQD7e2CQsYLAdCnY=,tag:3duziYwr7PoGQILUuY8nBA==,type:comment] +APP_NAME=ENC[AES256_GCM,data:ldJf4P0iD9ziMVg=,iv:hiVl2whhd02yZCafzBfbxX5/EU/suvzO4kSiWho2oUo=,tag:qzrr57sTPX8HPyDVwVL4sw==,type:str] +SECRET_KEY=ENC[AES256_GCM,data:Pll2sBGZsUJ0,iv:Dz+rq47dV3TmJXIQu+P+TmKXKFYsxbkY7/5js1cPrWA=,tag:IVAValYSELDRUMisbMwbAQ==,type:str] +BASE_URL=ENC[AES256_GCM,data:50k/RqlZ1EHqGM4UkSmTaCsuJgyU4w==,iv:f8zKr2jkts4RsawA97hzICHwj9Quzgp+Dw8AhQ7GSWA=,tag:9KhNvwmoOtDyuIql7okeew==,type:str] +DEBUG=ENC[AES256_GCM,data:O0/uRF4=,iv:cZ+vyUuXjQOYYRf4l8lWS3JIWqL/w3pnlCTDPAZpB1E=,tag:OmJE9oJpzYzth0xwaMqADQ==,type:str] +#ENC[AES256_GCM,data:xmJc6WTb3yumHzvLeA==,iv:9jKuYaDgm4zR/DTswIMwsajV0s5UTe+AOX4Sue0GPCs=,tag:b/7H9js1HmFYjuQE4zJz8w==,type:comment] +ADMIN_EMAILS=ENC[AES256_GCM,data:dtEDXPbN5Y5q,iv:k1GSkJh+L4kOM8V0cGYnz0/CsmvwdVRNHk0qpBulSS0=,tag:rUpVgROj2qD8a5IufnBrJw==,type:str] +#ENC[AES256_GCM,data:S7Pdg9tcom3N,iv:OjmYk3pqbZHKPS1Y06w1y8BE7CU0y6Vx2wnio9tEhus=,tag:YAOGbrHQ+UOcdSQFWdiCDA==,type:comment] +DATABASE_PATH=ENC[AES256_GCM,data:qxQs7dG0RWMA1rs=,iv:5ZUyk02hCPQESr2vFz3mfnUhUF74LbO6YK5+HFBbxUQ=,tag:daQxiWAhzCB2cScjzjYwaA==,type:str] +#ENC[AES256_GCM,data:aWgKm9Y=,iv:8iT6GHSzWhM+fRX9PIY9wAs7lXj/ADS6eZK9BBSEdaQ=,tag:aSLsj52ybnod7Qfmx9BLQA==,type:comment] +MAGIC_LINK_EXPIRY_MINUTES=ENC[AES256_GCM,data:YSE=,iv:GYm1EWku7+OG+fCIbUHWsfYbnEQVNhlBmWBC1OCV1NA=,tag:L2kdm7tMWOO/cf+VDd+OdQ==,type:str] +SESSION_LIFETIME_DAYS=ENC[AES256_GCM,data:9Og=,iv:3nStZVZVB24aAtNrtLXZ0oIehTDyu2IzdXoMH59t+3o=,tag:+FQ4n1XeSS12zUGXt/1RKQ==,type:str] +#ENC[AES256_GCM,data:mtqp/c5zZxlcB4HrOrfi,iv:eJaN+ZnAIaNHF5iovcz0QynILq9GjqVcwoyN2ZhLmpI=,tag:WyXU7ho5T/CE609id9dOzA==,type:comment] +RESEND_API_KEY=ENC[AES256_GCM,data:U5aEnItbJ/Af,iv:7BTFimeMbPtK6ANXMr7VwO5TJ7IaRk+HAOZy+TEXMVI=,tag:sDhW5icVloSck1iafu3H0A==,type:str] +EMAIL_FROM=ENC[AES256_GCM,data:BTGeWUjG9qCBvRQr9kK5sfdzQ1CfuNgpkU/AL3Qu6GJ2ng==,iv:0XjqD8hCqleSJR2FrDajlnUul8o4GkK0f1MOP96MRkw=,tag:0PwZwxuBbUFYdiRYTlDffg==,type:str] +LEADS_EMAIL=ENC[AES256_GCM,data:jkpWqodUgR2QoB96zvE5aH/tA9Sh0nPcl75P3i43ecFILw==,iv:vNtB/9gdrTDm6vNIjnH6JShYyqmG7h9jd2uzwFwjhO8=,tag:cG5T3CwQfZO/jTYFnwJSgA==,type:str] +RESEND_WEBHOOK_SECRET=ENC[AES256_GCM,data:EQpvkWFyt8H7,iv:6QiZIDo5Ps39vf9MKkiqSJir7BH9zhoLREJ425y3FIs=,tag:kjO4dczb2E5FKfO6OVaQvw==,type:str] +#ENC[AES256_GCM,data:HW8JOkd7Hw==,iv:Qfwm2ZHT8TKANrLrRQqHnceQVUTiuzT2hSjLN8hSq5Q=,tag:hvVLmGGUBRlsm2qy9jxIvA==,type:comment] +PADDLE_API_KEY=ENC[AES256_GCM,data:d3rKjWFrFepp,iv:TGjG9VTC4pZFgnp5daE+jBrRCUJddqgRaV7rQ61llhU=,tag:KKaYPfUgLC58zhC8s3B4cQ==,type:str] +PADDLE_CLIENT_TOKEN=ENC[AES256_GCM,data:JPmeLZx16WuV,iv:52EczBQM+fvEQuzoY8Aon0RBZiLzf1vrbT9Kx+b/WUE=,tag:+abUTzCgxulamobp13PbWQ==,type:str] +PADDLE_WEBHOOK_SECRET=ENC[AES256_GCM,data:fk2PbtpwoGRB,iv:QOhOd4rKmVjMA1EUQUjSj/y/OM7I435K/s4aqShjQNw=,tag:RIfbUCXAQGmCiE9FODHgpA==,type:str] +PADDLE_NOTIFICATION_SETTING_ID=ENC[AES256_GCM,data:igRsm8JOO1SP,iv:vQgOZcMHt6YoE+U2d6tT8sILOwsTx3glHVBBatR6Sk8=,tag:1tApDyZmZNiwd3bVm0uZGw==,type:str] +PADDLE_ENVIRONMENT=ENC[AES256_GCM,data:A1qXlv+9hjdIug==,iv:nu9kRQZgGLFXXT2I5GaRzp13YgQxU2ucr9azEA4XTUQ=,tag:RBxwE2j9v/RCiEMIa+6ICw==,type:str] +#ENC[AES256_GCM,data:F3dSfSGV,iv:Zjzmp9Vb+LBkqV6xBIMF2cK8ON9crH3fHcOog4+LOpo=,tag:7V8E9ChwYY9ceTaYdg3Lbw==,type:comment] +UMAMI_API_URL=ENC[AES256_GCM,data:4nJZc/opX4rsqAxO6XxD1Es5ySMh7nUtcGt6Kg==,iv:DcmhRe1IJKS0tOFgdJQQv2A1kO5K8VVT8aW0Vq5hVlY=,tag:Sglu4nnAiLIzr+ovJ/hEKQ==,type:str] +UMAMI_API_TOKEN=ENC[AES256_GCM,data:Xv1eTWtiJ6PL,iv:9sYsI2dJaQt6gpC/ev0b2dSk48PzuojTg18xXnBSWvk=,tag:DAMDHk0b9IG7T9MpkpzAkQ==,type:str] +#ENC[AES256_GCM,data:wAePRqqMZL2oCJB812A=,iv:jaLmjd0GW2dnEQ3KgWcvAs7Q7aDwlCexM9W7pH27kss=,tag:h7/yIdc13+3pmqyCc0OPkg==,type:comment] +RATE_LIMIT_REQUESTS=ENC[AES256_GCM,data:W3Nt,iv:ycMAxrPq44S6qezQIa50rc7GDplo1YvAO6VUERGQUxA=,tag:uzendLuSVbmSPcVPEgLiqQ==,type:str] +RATE_LIMIT_WINDOW=ENC[AES256_GCM,data:r8o=,iv:m5uKo3N8mb7FWI70SgaaHSyC3CNeD8XxjEx8ENit9uI=,tag:gKXEXsIwtBr3sm7xqLRHIw==,type:str] +#ENC[AES256_GCM,data:E6JgKjxuqFdPtVEv6Xiz1kqcT4ar,iv:hL7P7/X7nEqFwnlf72QEeHhViQ17HZbsCP/M4gcTJiA=,tag:FjCPSvrBboCWjfIS/fab0A==,type:comment] +LITESTREAM_R2_BUCKET=ENC[AES256_GCM,data:opg8kQY3PKnZ,iv:lPHUBDwHgBulOyt9WWgZhBQae8t2WKYvLHSFQrG3N/w=,tag:qtyIz4fbh40aLp7ZawBJiA==,type:str] +LITESTREAM_R2_ACCESS_KEY_ID=ENC[AES256_GCM,data:6jaEysPtRal7,iv:s5aLx7LdZ3ZLA9oL5vXXDfDDGI7gd5/CukNrMpPLJNk=,tag:Igp3bqW52raBfEeUaUvZ7A==,type:str] +LITESTREAM_R2_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:QfXhwh9L2rhr,iv:OaYlzTiu4onCNu5HfytYTCJa5p2QLShhO5j5Y038IOs=,tag:i13aQ2ICePyCU/Ob+EA7Nw==,type:str] +LITESTREAM_R2_ENDPOINT=ENC[AES256_GCM,data:hLneNsFmgQ6+,iv:RNefJ3QbviHPURxcK2xYJU7qWpMfWInCxYQ/4xDIwfw=,tag:FhMiHGrNcsXaSmdG4NXgfQ==,type:str] +#ENC[AES256_GCM,data:YGV2exKdGOUkblNZZos=,iv:NuabFM/gNHIzYmDMRZ2tglFYdMPVFuHFGd+AAWvvu6Q=,tag:gZRoNNEmjL9v3nC8j9YkHw==,type:comment] +DUCKDB_PATH=ENC[AES256_GCM,data:GgOEQ5B1KeQrVavhoMU/JGXcVu3H,iv:XY8JiaosxaUDv5PwizrZFWuNKMSOeuE3cfVyp51r++8=,tag:RnoDE5+7WQolFLejfRZ//w==,type:str] +SERVING_DUCKDB_PATH=ENC[AES256_GCM,data:U2X9KmlgnWXM9uCfhHCJ03HMGCLm,iv:KHHdBTq+ct4AG7Jt4zLog/5jbDC7LvHA6KzWNTDS/Yw=,tag:m5uIG/bS4vaBooSYoYa6SA==,type:str] +LANDING_DIR=ENC[AES256_GCM,data:NkEmV8LOwEiN9Sal,iv:mQHBVT6lNoEEEVbl7a5bNN5qoF/LvTyWXQvvkv/z/B0=,tag:IgA5A1nfF91fOBdYxEN71g==,type:str] +#ENC[AES256_GCM,data:jvZYm7ceM4jtNRg=,iv:nuv65SDTZiaVukVZ40seBZevpqP8uiKCgJyQcIrY524=,tag:cq6gB3vmJzJWIXCLHaIc9g==,type:comment] +REPO_DIR=ENC[AES256_GCM,data:ae8i6PpGFaiYFA/gGIhczg==,iv:nmsIRMPJYocIO6Z2Gz4OIzAOvSpdgDYmUaIr2hInFo0=,tag:EmAYG5NujnHg8lPaO/uAnQ==,type:str] +WORKFLOWS_PATH=ENC[AES256_GCM,data:sGU4l68Pbb1thsPyG104mWXWD+zJGTIcR/TqVbPmew==,iv:+xhGkX+ep4kFEAU65ELdDrfjrl/WyuaOi35JI3OB/zM=,tag:brauZhFq8twPXmvhZKjhDQ==,type:str] +ALERT_WEBHOOK_URL=ENC[AES256_GCM,data:4sXQk8zklruC525J279TUUatdDJQ43qweuoPhtpI82Y=,iv:1NT5IsslsZjo/0xU9OGFf717G56FnSkKSZ2L1+U3peU=,tag:bhZ67zlDiq7VaY47LFWOVw==,type:str] +NTFY_TOKEN=ENC[AES256_GCM,data:YlOxhsRJ8P1y4kk6ugWm41iyRCsM6oAWjvbU9lGcD0A=,iv:JZXOvi3wTOPV9A46c7fMiqbszNCvXkOgh9i/H1hob24=,tag:8xnPimgy7sesOAnxhaXmpg==,type:str] +SUPERVISOR_GIT_PULL=ENC[AES256_GCM,data:mg==,iv:KgqMVYj12FjOzWxtA1T0r0pqCDJ6MtHzMjE+4W/W+s4=,tag:czFaOqhHG8nqrQ8AZ8QiGw==,type:str] +#ENC[AES256_GCM,data:hzAZvCWc4RTk290=,iv:RsSI4OpAOQGcFVpfXDZ6t705yWmlO0JEWwWF5uQu9As=,tag:UPqFtA2tXiSa0vzJAv8qXg==,type:comment] +PROXY_URLS=ENC[AES256_GCM,data:L2Oobpi6Pq8m,iv:14mXi+8mLv2e20IKVL0VlxZiHW/1BmeQP4a6ns5930g=,tag:pVJasNjv6N/UApVm+KD+XA==,type:str] +RECHECK_WINDOW_MINUTES=ENC[AES256_GCM,data:L2s=,iv:fV3mCKmK5fxUmIWRePELBDAPTb8JZqasVIhnAl55kYw=,tag:XL+PO6sblz/7WqHC3dtk1w==,type:str] +#ENC[AES256_GCM,data:RC+t2vqLwLjapdAUql8rQls=,iv:Kkiz3ND0g0MRAgcPJysIYMzSQS96Rq+3YP5yO7yWfIY=,tag:Y6TbZd81ihIwn+U515qd1g==,type:comment] +GSC_SERVICE_ACCOUNT_PATH=ENC[AES256_GCM,data:Vki6yHk+gd4n,iv:rxzKvwrGnAkLcpS41EZ097E87NrIpNZGFfl4iXFvr40=,tag:EZkBJpCq5rSpKYVC4H3JHQ==,type:str] +GSC_SITE_URL=ENC[AES256_GCM,data:K0i1xRym+laMP6kgOMEfUyoAn2eNgQ==,iv:kyb+grzFq1e5CG/0NJRO3LkSXexOuCK07uJYApAdWsA=,tag:faljHqYjGTgrR/Zbh27/Yw==,type:str] +BING_WEBMASTER_API_KEY=ENC[AES256_GCM,data:kSQxJOpsYCuJ,iv:Kc4jJpOd64PATeBjidNHTwBr/bNnCeqsTrUqAAYM5Vs=,tag:4jBxqgpyomzMLwiC9XpfVQ==,type:str] +BING_SITE_URL=ENC[AES256_GCM,data:M33VI97DyxH8gRR3ZUXoXg4QrEv5og==,iv:GxZtwfbBVihUbp6XNQKzAalhO1GfQF1l1j1MeEIBCFQ=,tag:9njlBp4v684PeFl3HebyIg==,type:str] +#ENC[AES256_GCM,data:OTUMKNkRW0zrupNppXthwE1oieILhNjM+cjx5hFn69g=,iv:48ID2qtSe9ggD2X+G/iUqp3v2uwEc7fZw8lxHIvVXmk=,tag:okBn0Npk1K9dDOFWA/AB1A==,type:comment] +GEONAMES_USERNAME=ENC[AES256_GCM,data:UXd/S2TzXPiGmLY=,iv:OMURM5E6SFEsaqroUlH76DEnr7C/ujNk9UQnbWT0hK4=,tag:VsjjS12QDbudiEhdAQ/OCQ==,type:str] +CENSUS_API_KEY= +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqck9GdHVkUmIzNnlvMW5k\nVkNtazZ0ZytzZ25vMU5SckdFLzcrTFNYOVZZCmNjbU9yV0lTRlB5cEpMVC81QTdu\nS2ZDc0ZkNnRBNFhFMEN1bjY3YVhwZEEKLS0tIGE5TEdYenVOV1IwcE0wYnlKNElF\ncXV1K0xuczZzZ3JnL1lrSC9QWHIwNGsKfW4ARke6Cj83BpQc8weayL3v8SVgQ+Fp\n99aVWp103O1fumksR1w4u0X7fSNRrgAmpY/yyZuEvsoIY8ELFVcqgQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_0__map_recipient=age1f5002gj4s78jju45jd28kuejtcfhn5cdujz885fl7z2p9ym68pnsgky87a -sops_lastmodified=2026-02-24T20:32:28Z -sops_mac=ENC[AES256_GCM,data:htupj3WWPJ9KAxkgnsFyX8xnnfBeSna3nEjMA/RoFaDLBxkfFhXJOqiGTGzYAnsoc6KKxSdLy1Raa9wRqmmM0hmSqptBr/9axGthNmTg4m8UAgCzCG/ZJSL+hlTvmnL7Y2p3ryvk9w8Tw8jchdbFWgP5C6wmA1YAMOVBW+BfHxo=,iv:5NUe020D/1j8ISDFhZGyW8pobsKQtga2DJCmIV7yyIA=,tag:TlcQaU6z9ktkVJEduyoWtA==,type:str] +sops_lastmodified=2026-02-24T21:29:26Z +sops_mac=ENC[AES256_GCM,data:zYvusl8/pvL6FwXAtsKi4BhuiDt8KaZPNHXkw0ywIOgNFG5mvcQozcDj42+TIo+Yuum1o7WHqshKc70w0Mq4fskq3TsjVnjWgw7xYRr5s3ylN5ZknbbCoMP4cp6YrkNCe/8hR64miguYqqEQlf9NdgL52uamF5lV5irI/EtLouw=,iv:RcL2b8ccnMxKhXxAocTG9G6gv2BkTb++MUpkFK8MfbM=,tag:+0avRrQjNOHDUeAV1dLW3g==,type:str] sops_unencrypted_suffix=_unencrypted sops_version=3.12.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e4a58b..4f1ae3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). queries, geometry columns). - **SOPS secrets** — `GEONAMES_USERNAME=padelnomics` and `CENSUS_API_KEY` added to both `.env.dev.sops` and `.env.prod.sops`. +- **Crash-safe partial JSONL** — `utils.load_partial_results()` and `flush_partial_batch()` + provide a generic opt-in mechanism for incremental progress flushing during long extractions. + Any extractor processing items one-by-one can flush every N records and resume from a + `.partial.jsonl` sidecar file after a crash. - **Methodology page updated** — `/en/market-score` now documents both scores with: Two Scores intro section, component cards for each score (4 Marktreife + 5 Marktpotenzial), score band interpretations, expanded FAQ (7 entries). Section headings use the padelnomics @@ -52,6 +56,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). First "padelnomics Market Score" mention in each article template now links to the methodology page (hub-and-spoke internal linking). +### Changed +- **`EXTRACT_WORKERS` env var removed** — worker count is now derived from `PROXY_URLS` length + (one worker per proxy). No proxies → single-threaded. No manual tuning needed. +- **Playtomic tenants extractor** — parallel batch page fetching when proxies are configured. + Each page in a batch fires concurrently using its own session + proxy. Expected speedup: + ~2.5 min → ~15 s with 10 Webshare datacenter proxies. +- **Playtomic availability extractor** — three performance changes: + 1. No per-request `time.sleep()` on success when a proxy is active (throttle only when + running direct). Retry/backoff sleeps for 429 and 5xx responses are unchanged. + 2. Worker count auto-detected from proxy count (drops `EXTRACT_WORKERS`). + 3. True crash resumption via `.partial.jsonl` sidecar: progress flushed every 50 venues, + resume skips already-fetched venues and merges prior results into the final file. + ### Fixed - **`datetime.utcnow()` deprecation warnings** — replaced all 94 occurrences across 22 files (source + tests) with `utcnow()` / `utcnow_iso()` helpers diff --git a/extract/padelnomics_extract/src/padelnomics_extract/playtomic_availability.py b/extract/padelnomics_extract/src/padelnomics_extract/playtomic_availability.py index 6372191..180bcba 100644 --- a/extract/padelnomics_extract/src/padelnomics_extract/playtomic_availability.py +++ b/extract/padelnomics_extract/src/padelnomics_extract/playtomic_availability.py @@ -5,8 +5,13 @@ unauthenticated /v1/availability endpoint for each venue's next-day slots. This is the highest-value source: daily snapshots enable occupancy rate estimation, pricing benchmarking, and demand signal detection. -Parallel mode: set EXTRACT_WORKERS=N and PROXY_URLS=... to fetch N venues -concurrently (one proxy per worker). Without proxies, runs single-threaded. +Parallel mode: worker count is derived from PROXY_URLS (one worker per proxy). +Without proxies, runs single-threaded with per-request throttling. + +Crash resumption: progress is flushed to a .partial.jsonl sidecar file every +PARTIAL_FLUSH_SIZE records. On restart the already-fetched venues are skipped +and prior results are merged into the final file. At most PARTIAL_FLUSH_SIZE +records (a few seconds of work with 10 workers) are lost on crash. Recheck mode: re-queries venues with slots starting within the next 90 minutes. Writes a separate recheck file for more accurate occupancy measurement. @@ -29,7 +34,7 @@ import niquests from ._shared import HTTP_TIMEOUT_SECONDS, USER_AGENT, run_extractor, setup_logging from .proxy import load_fallback_proxy_urls, load_proxy_urls, make_tiered_cycler -from .utils import get_last_cursor, landing_path, write_gzip_atomic +from .utils import flush_partial_batch, landing_path, load_partial_results, write_gzip_atomic logger = setup_logging("padelnomics.extract.playtomic_availability") @@ -40,7 +45,6 @@ AVAILABILITY_URL = "https://api.playtomic.io/v1/availability" THROTTLE_SECONDS = 1 MAX_VENUES_PER_RUN = 20_000 MAX_RETRIES_PER_VENUE = 2 -MAX_WORKERS = int(os.environ.get("EXTRACT_WORKERS", "1")) RECHECK_WINDOW_MINUTES = int(os.environ.get("RECHECK_WINDOW_MINUTES", "90")) CIRCUIT_BREAKER_THRESHOLD = int(os.environ.get("CIRCUIT_BREAKER_THRESHOLD") or "10") @@ -49,6 +53,9 @@ CIRCUIT_BREAKER_THRESHOLD = int(os.environ.get("CIRCUIT_BREAKER_THRESHOLD") or " # batch still complete. PARALLEL_BATCH_SIZE = 100 +# Flush partial results to disk every N records — lose at most this many on crash. +PARTIAL_FLUSH_SIZE = 50 + # Thread-local storage for per-worker sessions _thread_local = threading.local() @@ -85,22 +92,6 @@ def _load_tenant_ids(landing_dir: Path) -> list[str]: return ids -def _parse_resume_cursor(cursor: str | None, target_date: str) -> int: - """Parse cursor_value to find resume index. Returns 0 if no valid cursor.""" - if not cursor: - return 0 - parts = cursor.split(":", 1) - if len(parts) != 2: - return 0 - cursor_date, cursor_index = parts - if cursor_date != target_date: - return 0 - try: - return int(cursor_index) - except ValueError: - return 0 - - # --------------------------------------------------------------------------- # Per-venue fetch (used by both serial and parallel modes) # --------------------------------------------------------------------------- @@ -150,7 +141,8 @@ def _fetch_venue_availability( continue resp.raise_for_status() - time.sleep(THROTTLE_SECONDS) + if not proxy_url: + time.sleep(THROTTLE_SECONDS) return {"tenant_id": tenant_id, "slots": resp.json()} except niquests.exceptions.RequestException as e: @@ -178,6 +170,7 @@ def _fetch_venues_parallel( worker_count: int, cycler: dict, fallback_urls: list[str], + on_result=None, ) -> tuple[list[dict], int]: """Fetch availability for multiple venues in parallel. @@ -185,6 +178,9 @@ def _fetch_venues_parallel( completes, checks the circuit breaker: if it opened and there is no fallback configured, stops submitting further batches. + on_result: optional callable(result: dict) invoked inside the lock for + each successful result — used for incremental partial-file flushing. + Returns (venues_data, venues_errored). """ venues_data: list[dict] = [] @@ -216,6 +212,8 @@ def _fetch_venues_parallel( if result is not None: venues_data.append(result) cycler["record_success"]() + if on_result is not None: + on_result(result) else: venues_errored += 1 cycler["record_failure"]() @@ -265,41 +263,56 @@ def extract( logger.info("Already have %s — skipping", dest) return {"files_written": 0, "files_skipped": 1, "bytes_written": 0} - # Resume from cursor if crashed mid-run - last_cursor = get_last_cursor(conn, EXTRACTOR_NAME) - resume_index = _parse_resume_cursor(last_cursor, target_date) - if resume_index > 0: - logger.info("Resuming from index %d (cursor: %s)", resume_index, last_cursor) + # Crash resumption: load already-fetched venues from partial file + partial_path = dest.with_suffix(".partial.jsonl") + prior_results, already_done = load_partial_results(partial_path, id_key="tenant_id") + if already_done: + logger.info("Resuming: %d venues already fetched from partial file", len(already_done)) - venues_to_process = tenant_ids[:MAX_VENUES_PER_RUN] - if resume_index > 0: - venues_to_process = venues_to_process[resume_index:] + all_venues_to_process = tenant_ids[:MAX_VENUES_PER_RUN] + venues_to_process = [tid for tid in all_venues_to_process if tid not in already_done] # Set up tiered proxy cycler with circuit breaker proxy_urls = load_proxy_urls() fallback_urls = load_fallback_proxy_urls() - worker_count = min(MAX_WORKERS, len(proxy_urls)) if proxy_urls else 1 + worker_count = len(proxy_urls) if proxy_urls else 1 cycler = make_tiered_cycler(proxy_urls, fallback_urls, CIRCUIT_BREAKER_THRESHOLD) start_min_str = start_min.strftime("%Y-%m-%dT%H:%M:%S") start_max_str = start_max.strftime("%Y-%m-%dT%H:%M:%S") + # Partial file for incremental crash-safe progress + partial_file = open(partial_path, "a") # noqa: SIM115 + partial_lock = threading.Lock() + pending_batch: list[dict] = [] + + def _on_result(result: dict) -> None: + # Called inside _fetch_venues_parallel's lock — no additional locking needed. + # In serial mode, called single-threaded — also safe without extra locking. + pending_batch.append(result) + if len(pending_batch) >= PARTIAL_FLUSH_SIZE: + flush_partial_batch(partial_file, partial_lock, pending_batch) + pending_batch.clear() + + new_venues_data: list[dict] = [] + venues_errored = 0 + if worker_count > 1: logger.info("Parallel mode: %d workers, %d proxies", worker_count, len(proxy_urls)) - venues_data, venues_errored = _fetch_venues_parallel( + new_venues_data, venues_errored = _fetch_venues_parallel( venues_to_process, start_min_str, start_max_str, worker_count, cycler, fallback_urls, + on_result=_on_result, ) else: logger.info("Serial mode: 1 worker, %d venues", len(venues_to_process)) - venues_data = [] - venues_errored = 0 for i, tenant_id in enumerate(venues_to_process): result = _fetch_venue_availability( tenant_id, start_min_str, start_max_str, cycler["next_proxy"](), ) if result is not None: - venues_data.append(result) + new_venues_data.append(result) cycler["record_success"]() + _on_result(result) else: venues_errored += 1 circuit_opened = cycler["record_failure"]() @@ -313,7 +326,14 @@ def extract( i + 1, len(venues_to_process), venues_errored, ) - # Write consolidated file + # Final flush of any remaining partial batch + if pending_batch: + flush_partial_batch(partial_file, partial_lock, pending_batch) + pending_batch.clear() + partial_file.close() + + # Consolidate prior (resumed) + new results into final file + venues_data = prior_results + new_venues_data captured_at = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ") payload = json.dumps({ "date": target_date, @@ -324,6 +344,9 @@ def extract( }).encode() bytes_written = write_gzip_atomic(dest, payload) + if partial_path.exists(): + partial_path.unlink() + logger.info( "%d venues scraped (%d errors) -> %s (%s bytes)", len(venues_data), venues_errored, dest, f"{bytes_written:,}", @@ -333,7 +356,7 @@ def extract( "files_written": 1, "files_skipped": 0, "bytes_written": bytes_written, - "cursor_value": f"{target_date}:{len(tenant_ids[:MAX_VENUES_PER_RUN])}", + "cursor_value": f"{target_date}:{len(all_venues_to_process)}", } @@ -426,7 +449,7 @@ def extract_recheck( # Set up tiered proxy cycler with circuit breaker proxy_urls = load_proxy_urls() fallback_urls = load_fallback_proxy_urls() - worker_count = min(MAX_WORKERS, len(proxy_urls)) if proxy_urls else 1 + worker_count = len(proxy_urls) if proxy_urls else 1 cycler = make_tiered_cycler(proxy_urls, fallback_urls, CIRCUIT_BREAKER_THRESHOLD) if worker_count > 1 and len(venues_to_recheck) > 10: diff --git a/extract/padelnomics_extract/src/padelnomics_extract/playtomic_tenants.py b/extract/padelnomics_extract/src/padelnomics_extract/playtomic_tenants.py index b12932c..8feb5c4 100644 --- a/extract/padelnomics_extract/src/padelnomics_extract/playtomic_tenants.py +++ b/extract/padelnomics_extract/src/padelnomics_extract/playtomic_tenants.py @@ -10,8 +10,13 @@ API notes (discovered 2026-02): - `size=100` is the maximum effective page size - ~14K venues globally as of Feb 2026 -Rate: 1 req / 2 s when running direct (see docs/data-sources-inventory.md §1.2). - No throttle when PROXY_URLS is set — IP rotation removes per-IP rate concern. +Parallel mode: when PROXY_URLS is set, fires batch_size = len(proxy_urls) +pages concurrently. Each page gets its own fresh session + proxy. Pages beyond +the last one return empty lists (safe — just triggers the done condition). +Without proxies, falls back to single-threaded with THROTTLE_SECONDS between +pages. + +Rate: 1 req / 2 s per IP (see docs/data-sources-inventory.md §1.2). Landing: {LANDING_DIR}/playtomic/{year}/{month}/tenants.json.gz """ @@ -19,11 +24,12 @@ Landing: {LANDING_DIR}/playtomic/{year}/{month}/tenants.json.gz import json import sqlite3 import time +from concurrent.futures import ThreadPoolExecutor, as_completed from pathlib import Path import niquests -from ._shared import HTTP_TIMEOUT_SECONDS, run_extractor, setup_logging +from ._shared import HTTP_TIMEOUT_SECONDS, USER_AGENT, run_extractor, setup_logging from .proxy import load_proxy_urls, make_round_robin_cycler from .utils import landing_path, write_gzip_atomic @@ -37,6 +43,30 @@ PAGE_SIZE = 100 MAX_PAGES = 500 # safety bound — ~50K venues max, well above current ~14K +def _fetch_one_page(proxy_url: str | None, page: int) -> tuple[int, list[dict]]: + """Fetch a single page using a fresh session with the given proxy. + + Returns (page, tenants_list). Raises on HTTP error. + """ + s = niquests.Session() + s.headers["User-Agent"] = USER_AGENT + if proxy_url: + s.proxies = {"http": proxy_url, "https": proxy_url} + params = {"sport_ids": "PADEL", "size": PAGE_SIZE, "page": page} + resp = s.get(PLAYTOMIC_TENANTS_URL, params=params, timeout=HTTP_TIMEOUT_SECONDS) + resp.raise_for_status() + tenants = resp.json() + assert isinstance(tenants, list), f"Expected list from Playtomic API, got {type(tenants)}" + return (page, tenants) + + +def _fetch_pages_parallel(pages: list[int], next_proxy) -> list[tuple[int, list[dict]]]: + """Fetch multiple pages concurrently. Returns [(page_num, tenants_list), ...].""" + with ThreadPoolExecutor(max_workers=len(pages)) as pool: + futures = [pool.submit(_fetch_one_page, next_proxy(), p) for p in pages] + return [f.result() for f in as_completed(futures)] + + def extract( landing_dir: Path, year_month: str, @@ -49,54 +79,63 @@ def extract( dest = dest_dir / "tenants.json.gz" proxy_urls = load_proxy_urls() - cycler = make_round_robin_cycler(proxy_urls) if proxy_urls else None - if cycler: - logger.info("proxy rotation enabled (%d proxies, no throttle)", len(proxy_urls)) + next_proxy = make_round_robin_cycler(proxy_urls) if proxy_urls else None + batch_size = len(proxy_urls) if proxy_urls else 1 + + if next_proxy: + logger.info("Parallel mode: %d pages per batch (%d proxies)", batch_size, len(proxy_urls)) else: - logger.info("no proxies configured — throttle %ds per page", THROTTLE_SECONDS) + logger.info("Serial mode: 1 page at a time (no proxies)") all_tenants: list[dict] = [] seen_ids: set[str] = set() + page = 0 + done = False - for page in range(MAX_PAGES): - if cycler: - proxy = cycler() - if proxy: - session.proxies = {"http": proxy, "https": proxy} + while not done and page < MAX_PAGES: + batch_end = min(page + batch_size, MAX_PAGES) + pages_to_fetch = list(range(page, batch_end)) - params = { - "sport_ids": "PADEL", - "size": PAGE_SIZE, - "page": page, - } + if next_proxy and len(pages_to_fetch) > 1: + logger.info( + "Fetching pages %d-%d in parallel (%d workers, total so far: %d)", + page, batch_end - 1, len(pages_to_fetch), len(all_tenants), + ) + results = _fetch_pages_parallel(pages_to_fetch, next_proxy) + else: + # Serial: reuse the shared session, throttle between pages + page_num = pages_to_fetch[0] + logger.info("GET page=%d (total so far: %d)", page_num, len(all_tenants)) + params = {"sport_ids": "PADEL", "size": PAGE_SIZE, "page": page_num} + resp = session.get(PLAYTOMIC_TENANTS_URL, params=params, timeout=HTTP_TIMEOUT_SECONDS) + resp.raise_for_status() + tenants = resp.json() + assert isinstance(tenants, list), ( + f"Expected list from Playtomic API, got {type(tenants)}" + ) + results = [(page_num, tenants)] - logger.info("GET page=%d (total so far: %d)", page, len(all_tenants)) + # Process pages in order so the done-detection on < PAGE_SIZE is deterministic + for p, tenants in sorted(results): + new_count = 0 + for tenant in tenants: + tid = tenant.get("tenant_id") or tenant.get("id") + if tid and tid not in seen_ids: + seen_ids.add(tid) + all_tenants.append(tenant) + new_count += 1 - resp = session.get(PLAYTOMIC_TENANTS_URL, params=params, timeout=HTTP_TIMEOUT_SECONDS) - resp.raise_for_status() + logger.info( + "page=%d got=%d new=%d total=%d", p, len(tenants), new_count, len(all_tenants), + ) - tenants = resp.json() - assert isinstance(tenants, list), ( - f"Expected list from Playtomic API, got {type(tenants)}" - ) + # Last page — fewer than PAGE_SIZE results means we've exhausted the list + if len(tenants) < PAGE_SIZE: + done = True + break - new_count = 0 - for tenant in tenants: - tid = tenant.get("tenant_id") or tenant.get("id") - if tid and tid not in seen_ids: - seen_ids.add(tid) - all_tenants.append(tenant) - new_count += 1 - - logger.info( - "page=%d got=%d new=%d total=%d", page, len(tenants), new_count, len(all_tenants) - ) - - # Last page — fewer than PAGE_SIZE results means we've exhausted the list - if len(tenants) < PAGE_SIZE: - break - - if not cycler: + page = batch_end + if not next_proxy: time.sleep(THROTTLE_SECONDS) payload = json.dumps({"tenants": all_tenants, "count": len(all_tenants)}).encode() diff --git a/extract/padelnomics_extract/src/padelnomics_extract/proxy.py b/extract/padelnomics_extract/src/padelnomics_extract/proxy.py index 7d280ee..0e8c82e 100644 --- a/extract/padelnomics_extract/src/padelnomics_extract/proxy.py +++ b/extract/padelnomics_extract/src/padelnomics_extract/proxy.py @@ -3,10 +3,6 @@ Proxies are configured via the PROXY_URLS environment variable (comma-separated). When unset, all functions return None/no-op — extractors fall back to direct requests. -Two routing modes: - round-robin — distribute requests evenly across proxies (default) - sticky — same key always maps to same proxy (for session-tracked sites) - Tiered proxy with circuit breaker: Primary tier (PROXY_URLS) is used by default — typically cheap datacenter proxies. Fallback tier (PROXY_URLS_FALLBACK) activates once consecutive failures >= threshold. @@ -141,17 +137,3 @@ def make_tiered_cycler( "is_fallback_active": is_fallback_active, } - -def make_sticky_selector(proxy_urls: list[str]): - """Consistent-hash proxy selector — same key always maps to same proxy. - - Use when the target site tracks sessions by IP (e.g. Cloudflare). - Returns a callable: select_proxy(key: str) -> str | None - """ - if not proxy_urls: - return lambda key: None - - def select_proxy(key: str) -> str: - return proxy_urls[hash(key) % len(proxy_urls)] - - return select_proxy diff --git a/extract/padelnomics_extract/src/padelnomics_extract/utils.py b/extract/padelnomics_extract/src/padelnomics_extract/utils.py index 3cb2562..15777f0 100644 --- a/extract/padelnomics_extract/src/padelnomics_extract/utils.py +++ b/extract/padelnomics_extract/src/padelnomics_extract/utils.py @@ -7,7 +7,9 @@ if you add multiple data sources, extract them to a shared workspace package. import gzip import hashlib +import json import sqlite3 +import threading from pathlib import Path # --------------------------------------------------------------------------- @@ -117,6 +119,50 @@ def content_hash(data: bytes, prefix_bytes: int = 8) -> str: return hashlib.sha256(data).hexdigest()[:prefix_bytes] +def load_partial_results(partial_path: Path, id_key: str) -> tuple[list[dict], set[str]]: + """Load already-completed records from a partial JSONL file (crash recovery). + + Returns (records, seen_ids). If the file doesn't exist, returns ([], set()). + Gracefully handles a truncated last line from a mid-write crash. + """ + records: list[dict] = [] + seen_ids: set[str] = set() + if not partial_path.exists(): + return records, seen_ids + with open(partial_path) as f: + for line in f: + line = line.strip() + if not line: + continue + try: + record = json.loads(line) + records.append(record) + rid = record.get(id_key) + if rid: + seen_ids.add(rid) + except json.JSONDecodeError: + break # truncated last line from crash — skip it + return records, seen_ids + + +def flush_partial_batch( + partial_file, + lock: threading.Lock, + batch: list[dict], +) -> None: + """Thread-safe batch write of JSON records to the partial JSONL file. + + Writes all records in one lock acquisition with a single flush. + Call with batches of ~50 records for good I/O throughput vs crash safety tradeoff. + On crash, at most one batch worth of records is lost. + """ + assert batch, "batch must not be empty" + with lock: + for record in batch: + partial_file.write(json.dumps(record, separators=(",", ":")) + "\n") + partial_file.flush() + + def write_gzip_atomic(path: Path, data: bytes) -> int: """Gzip compress data and write to path atomically via .tmp sibling.