diff --git a/install/install.py b/install/install.py index 071cff5cf..ac0224a1e 100644 --- a/install/install.py +++ b/install/install.py @@ -5228,17 +5228,20 @@ user_query = SELECT email as user, password, 'vmail' as uid, 'vmail' as gid, '/h cronFile = open(cronPath, "w") - # Randomize acme.sh cron schedule to avoid traffic spikes to Let's Encrypt - # Generate random hour (0-23) and minute (0-59) for each installation + # Randomize acme.sh and renew.py cron schedules to avoid traffic spikes to Let's Encrypt + # Each installation gets a random day (0-6 Sun-Sat), hour, and minute to spread load acme_hour = random.randint(0, 23) acme_minute = random.randint(0, 59) + renew_weekday = random.randint(0, 6) # 0=Sun, 1=Mon, ..., 6=Sat + renew_hour = random.randint(0, 23) + renew_minute = random.randint(0, 59) content = """ 0 * * * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/findBWUsage.py >/dev/null 2>&1 0 * * * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/postfixSenderPolicy/client.py hourlyCleanup >/dev/null 2>&1 0 0 1 * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/postfixSenderPolicy/client.py monthlyCleanup >/dev/null 2>&1 0 2 * * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/upgradeCritical.py >/dev/null 2>&1 -0 0 * * 4 /usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/renew.py >/dev/null 2>&1 +%d %d * * %d /usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/renew.py >/dev/null 2>&1 %d %d * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null 0 0 * * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/IncBackups/IncScheduler.py Daily 0 0 * * 0 /usr/local/CyberCP/bin/python /usr/local/CyberCP/IncBackups/IncScheduler.py Weekly @@ -5252,7 +5255,7 @@ user_query = SELECT email as user, password, 'vmail' as uid, 'vmail' as gid, '/h 0 0 * * 0 /usr/local/CyberCP/bin/python /usr/local/CyberCP/IncBackups/IncScheduler.py '1 Week' */3 * * * * if ! find /home/*/public_html/ -maxdepth 2 -type f -newer /usr/local/lsws/cgid -name '.htaccess' -exec false {} +; then /usr/local/lsws/bin/lswsctrl restart; fi -""" % (acme_minute, acme_hour) +""" % (renew_minute, renew_hour, renew_weekday, acme_minute, acme_hour) cronFile.write(content) cronFile.close() diff --git a/plogical/renew.py b/plogical/renew.py index f6a9145d1..6f9c9814a 100755 --- a/plogical/renew.py +++ b/plogical/renew.py @@ -16,6 +16,51 @@ import OpenSSL from plogical.virtualHostUtilities import virtualHostUtilities from plogical.processUtilities import ProcessUtilities +def _update_ssl_renewal_schedule_file(): + """Write SSL renewal schedule to world-readable file for web UI (lscpd can't read root crontab).""" + try: + from datetime import datetime, timedelta + config_path = '/usr/local/CyberCP/ssl_renewal_schedule.conf' + cron_paths = ['/var/spool/cron/root', '/var/spool/cron/crontabs/root'] + cron_content = None + for path in cron_paths: + if os.path.exists(path): + with open(path, 'r') as f: + cron_content = f.read() + break + if not cron_content: + return + renew_hour, renew_minute, renew_weekday_cron = 0, 0, 4 + for line in cron_content.splitlines(): + line = line.strip() + if 'renew.py' in line and not line.startswith('#'): + parts = line.split() + if len(parts) >= 5: + try: + renew_minute = int(parts[0]) if parts[0].isdigit() else 0 + renew_hour = int(parts[1]) if parts[1].isdigit() else 0 + renew_weekday_cron = int(parts[4]) if parts[4].isdigit() and 0 <= int(parts[4]) <= 7 else 4 + except (ValueError, IndexError): + pass + elif len(parts) >= 2: + renew_minute = int(parts[0]) if parts[0].isdigit() else 0 + renew_hour = int(parts[1]) if parts[1].isdigit() else 0 + break + now = datetime.now() + target_weekday = (renew_weekday_cron - 1) % 7 if renew_weekday_cron else 6 + days_until = (target_weekday - now.weekday()) % 7 + if days_until == 0 and (now.hour > renew_hour or (now.hour == renew_hour and now.minute >= renew_minute)): + days_until = 7 + next_run = now.replace(hour=renew_hour, minute=renew_minute, second=0, microsecond=0) + next_run += timedelta(days=days_until) + schedule_str = next_run.strftime('%B %d, %Y %I:%M %p') + with open(config_path, 'w') as f: + f.write(schedule_str) + os.chmod(config_path, 0o644) + except Exception as e: + logging.writeToFile(f'_update_ssl_renewal_schedule_file: {str(e)}', 1) + + class Renew: def _check_and_renew_ssl(self, domain: str, path: str, admin_email: str, is_child: bool = False) -> None: """Helper method to check and renew SSL for a domain.""" @@ -114,6 +159,7 @@ class Renew: def SSLObtainer(self): try: + _update_ssl_renewal_schedule_file() logging.writeToFile('Running SSL Renew Utility') # Process main domains diff --git a/plogical/upgrade.py b/plogical/upgrade.py index 731877b1a..f96dabe12 100644 --- a/plogical/upgrade.py +++ b/plogical/upgrade.py @@ -5292,17 +5292,20 @@ vmail data = open(cronPath, 'r').read() if data.find('findBWUsage') == -1: - # Randomize acme.sh cron schedule to avoid traffic spikes to Let's Encrypt - # Generate random hour (0-23) and minute (0-59) for each installation + # Randomize acme.sh and renew.py cron schedules to avoid traffic spikes to Let's Encrypt + # Each installation gets a random day (0-6 Sun-Sat), hour, and minute to spread load acme_hour = random.randint(0, 23) acme_minute = random.randint(0, 59) + renew_weekday = random.randint(0, 6) # 0=Sun, 1=Mon, ..., 6=Sat + renew_hour = random.randint(0, 23) + renew_minute = random.randint(0, 59) content = """ 0 * * * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/findBWUsage.py >/dev/null 2>&1 0 * * * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/postfixSenderPolicy/client.py hourlyCleanup >/dev/null 2>&1 0 0 1 * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/postfixSenderPolicy/client.py monthlyCleanup >/dev/null 2>&1 0 2 * * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/upgradeCritical.py >/dev/null 2>&1 -0 0 * * 4 /usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/renew.py >/dev/null 2>&1 +%d %d * * %d /usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/renew.py >/dev/null 2>&1 %d %d * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null 0 1 * * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/manage.py ssl_reconcile --all >/dev/null 2>&1 */3 * * * * if ! find /home/*/public_html/ -maxdepth 2 -type f -newer /usr/local/lsws/cgid -name '.htaccess' -exec false {} +; then /usr/local/lsws/bin/lswsctrl restart; fi @@ -5310,7 +5313,7 @@ vmail """ writeToFile = open(cronPath, 'w') - writeToFile.write(content % (acme_minute, acme_hour)) + writeToFile.write(content % (renew_minute, renew_hour, renew_weekday, acme_minute, acme_hour)) writeToFile.close() if data.find('IncScheduler.py') == -1: @@ -5347,23 +5350,26 @@ vmail else: - # Randomize acme.sh cron schedule to avoid traffic spikes to Let's Encrypt - # Generate random hour (0-23) and minute (0-59) for each installation + # Randomize acme.sh and renew.py cron schedules to avoid traffic spikes to Let's Encrypt + # Each installation gets a random day (0-6 Sun-Sat), hour, and minute to spread load acme_hour = random.randint(0, 23) acme_minute = random.randint(0, 59) + renew_weekday = random.randint(0, 6) # 0=Sun, 1=Mon, ..., 6=Sat + renew_hour = random.randint(0, 23) + renew_minute = random.randint(0, 59) content = """ 0 * * * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/findBWUsage.py >/dev/null 2>&1 0 * * * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/postfixSenderPolicy/client.py hourlyCleanup >/dev/null 2>&1 0 0 1 * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/postfixSenderPolicy/client.py monthlyCleanup >/dev/null 2>&1 0 2 * * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/upgradeCritical.py >/dev/null 2>&1 -0 0 * * 4 /usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/renew.py >/dev/null 2>&1 +%d %d * * %d /usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/renew.py >/dev/null 2>&1 %d %d * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null 0 1 * * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/manage.py ssl_reconcile --all >/dev/null 2>&1 0 0 * * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/IncBackups/IncScheduler.py Daily 0 0 * * 0 /usr/local/CyberCP/bin/python /usr/local/CyberCP/IncBackups/IncScheduler.py Weekly * * * * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/manage.py run_scheduled_scans >/usr/local/lscp/logs/scheduled_scans.log 2>&1 -""" % (acme_minute, acme_hour) +""" % (renew_minute, renew_hour, renew_weekday, acme_minute, acme_hour) writeToFile = open(cronPath, 'w') writeToFile.write(content) writeToFile.close() diff --git a/websiteFunctions/templates/websiteFunctions/launchChild.html b/websiteFunctions/templates/websiteFunctions/launchChild.html index 93ebb4e1f..c9a7b6457 100644 --- a/websiteFunctions/templates/websiteFunctions/launchChild.html +++ b/websiteFunctions/templates/websiteFunctions/launchChild.html @@ -653,7 +653,7 @@
{% trans "Your SSL will expire in" %} {{ days }} {% trans "days" %}
+{% trans "Your SSL will expire in" %} {{ days }} {% trans "days" %}{% if renewal_when %} • {% trans "Renews" %} {{ renewal_when }}{% endif %}