#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ SSL 证书申请与自动续期脚本 使用 certbot 工具申请 Let's Encrypt 证书并配置 nginx Author: emotion-museum Created: 2026-03-17 Purpose: SSL 证书申请与自动续期 - 使用 certbot 配置 Let's Encrypt 证书 """ import argparse import subprocess import sys from pathlib import Path DOMAIN = "lifescript.happylifeos.com" CERT_PATH = f"/etc/letsencrypt/live/{DOMAIN}/fullchain.pem" KEY_PATH = f"/etc/letsencrypt/live/{DOMAIN}/privkey.pem" def log_info(msg): """输出信息日志""" print(f"\033[32m[INFO]\033[0m {msg}") def log_warn(msg): """输出警告日志""" print(f"\033[33m[WARN]\033[0m {msg}") def log_error(msg): """输出错误日志""" print(f"\033[31m[ERROR]\033[0m {msg}") def run_command(cmd, check=True): """执行 shell 命令 Args: cmd: 要执行的命令 check: 是否检查返回码,False 时总是返回 True Returns: bool: 命令是否执行成功 """ try: result = subprocess.run( cmd, shell=True, check=check, capture_output=False, text=True ) return result.returncode == 0 except subprocess.CalledProcessError as e: log_error(f"命令执行失败:{e}") return False def check_certbot(): """检查 certbot 是否安装 Returns: bool: certbot 是否已安装 """ log_info("检查 certbot 是否安装...") if not run_command("which certbot", check=False): log_error("certbot 未安装,请先安装:apt install certbot python3-certbot-nginx") return False log_info("certbot 已安装") return True def check_nginx(): """检查 nginx 配置是否正确 Returns: bool: nginx 配置是否正确 """ log_info("检查 nginx 配置...") if not run_command("nginx -t", check=False): log_error("nginx 配置有误,请先修复") return False log_info("nginx 配置正确") return True def apply_certificate(dry_run=False): """申请 SSL 证书 Args: dry_run: 是否使用 dry-run 模式 Returns: bool: 证书申请是否成功 """ log_info(f"开始为 {DOMAIN} 申请 SSL 证书...") cmd = f"certbot --nginx -d {DOMAIN} --non-interactive --agree-tos --email admin@happylifeos.com" if dry_run: cmd += " --dry-run" log_info(f"[DRY RUN] 执行:{cmd}") if run_command(cmd): log_info("证书申请成功") return True else: log_error("证书申请失败") return False def setup_auto_renewal(): """配置自动续期 Returns: bool: 配置是否成功 """ log_info("配置证书自动续期...") # 检查是否已存在 systemd timer if run_command("systemctl list-timers | grep certbot", check=False): log_info("certbot 自动续期 timer 已存在") return True # 启用 systemd timer if run_command("systemctl enable certbot.timer && systemctl start certbot.timer"): log_info("已启用 certbot 自动续期 timer") return True else: log_warn("systemd timer 配置失败,尝试配置 cron 任务") return setup_cron_renewal() def setup_cron_renewal(): """配置 cron 自动续期 Returns: bool: cron 配置是否成功 """ cron_job = "0 3 1 * * certbot renew --quiet --deploy-hook 'systemctl reload nginx'" log_info(f"添加 cron 任务:{cron_job}") # 添加到 crontab result = subprocess.run( f"(crontab -l 2>/dev/null | grep -v '{DOMAIN}'; echo '{cron_job}') | crontab -", shell=True, capture_output=True, text=True ) if result.returncode == 0: log_info("cron 任务添加成功") return True else: log_error("cron 任务添加失败") return False def verify_certificate(): """验证证书是否有效 Returns: bool: 证书是否有效 """ log_info("验证 SSL 证书...") cmd = f'echo | openssl s_client -connect {DOMAIN}:443 -servername {DOMAIN} 2>/dev/null | openssl x509 -noout -dates' result = subprocess.run(cmd, shell=True, capture_output=True, text=True) if result.returncode == 0: log_info(f"证书信息:\n{result.stdout}") return True else: log_warn("无法验证证书(可能域名 DNS 尚未生效)") return False def main(): """主函数 Returns: int: 退出码,0 表示成功,非 0 表示失败 """ parser = argparse.ArgumentParser(description="SSL 证书申请与自动续期脚本") parser.add_argument("--dry-run", action="store_true", help="模拟运行,不实际申请证书") parser.add_argument("--renew", action="store_true", help="手动续期证书") parser.add_argument("--verify", action="store_true", help="验证证书状态") parser.add_argument("--setup-renewal", action="store_true", help="仅配置自动续期") args = parser.parse_args() # 验证模式 if args.verify: verify_certificate() return 0 # 仅配置自动续期 if args.setup_renewal: setup_auto_renewal() return 0 # 手动续期 if args.renew: run_command("certbot renew --force-renewal") run_command("systemctl reload nginx") return 0 # 完整流程 log_info("=== SSL 证书申请流程 ===") if not check_certbot(): return 1 if not check_nginx(): return 1 if not apply_certificate(dry_run=args.dry_run): return 1 if not args.dry_run: setup_auto_renewal() verify_certificate() log_info("=== SSL 证书申请完成 ===") return 0 if __name__ == "__main__": sys.exit(main())