Files
peanut aa1e12c6b9 feat: 添加 SSL 证书申请与自动续期脚本
- 使用 certbot 申请 Let's Encrypt SSL 证书
- 自动配置 nginx HTTPS
- 支持 systemd timer 或 cron 自动续期
- 提供证书状态验证功能
- 支持 dry-run 模拟运行模式

Author: emotion-museum
Created: 2026-03-17
2026-03-17 23:52:19 +08:00

223 lines
5.7 KiB
Python

#!/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())