Files
happy-life-star/tools/deploy-ssl-cert.py
T
peanut 04d5024752 feat: 完成域名部署配置
- 创建 SSL 证书申请脚本 (tools/deploy-ssl-cert.py)
- 创建 nginx HTTPS 配置文件 (conf/nginx-emotion-museum-ssl.conf)
- 创建 nginx HTTP 修复配置 (conf/nginx-emotion-museum-fix.conf)
- 创建一键部署脚本 (deploy-domain.sh)
- 更新前端依赖并构建

部署验证:
- HTTPS 前端页面:200
- HTTPS 管理后台:200
- HTTP->HTTPS 跳转:301
- SSL 证书有效期:2026-06-16
2026-03-18 19:44:39 +08:00

382 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
SSL 证书申请和配置脚本 - 在服务器上执行
Author: Emotion Museum Team
Created: 2026-03-18
Purpose: 使用 certbot 申请 Let's Encrypt SSL 证书并配置自动续期
使用方法:
python3 deploy-ssl-cert.py # 申请证书
python3 deploy-ssl-cert.py --verify # 验证证书
python3 deploy-ssl-cert.py --renew # 手动续期
"""
import os
import sys
import subprocess
import argparse
from datetime import datetime
# 配置
DOMAIN = "lifescript.happylifeos.com"
EMAIL = "admin@happylifeos.com" # 请根据实际情况修改
CERT_PATH = f"/etc/letsencrypt/live/{DOMAIN}"
NGINX_SSL_CONF = "/etc/nginx/sites-available/lifescript.happylifeos.com.conf"
# 颜色定义
RED = '\033[0;31m'
GREEN = '\033[0;32m'
YELLOW = '\033[1;33m'
BLUE = '\033[0;34m'
NC = '\033[0m'
def log_info(msg):
print(f"{GREEN}[INFO]{NC} {msg}")
def log_warn(msg):
print(f"{YELLOW}[WARN]{NC} {msg}")
def log_error(msg):
print(f"{RED}[ERROR]{NC} {msg}")
def log_section(msg):
print("")
print(f"{BLUE}{'='*60}{NC}")
print(f"{BLUE}{msg}{NC}")
print(f"{BLUE}{'='*60}{NC}")
print("")
def run_command(cmd, capture=False):
"""执行 shell 命令"""
try:
if capture:
result = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
return result.returncode, result.stdout, result.stderr
else:
result = subprocess.run(cmd, shell=True)
return result.returncode, None, None
except Exception as e:
log_error(f"命令执行失败:{e}")
return 1, None, str(e)
def check_certbot():
"""检查 certbot 是否已安装"""
log_info("检查 certbot 是否已安装...")
returncode, stdout, stderr = run_command("which certbot", capture=True)
if returncode == 0:
log_info(f"certbot 已安装:{stdout.strip()}")
return True
else:
log_warn("certbot 未安装,尝试安装...")
return install_certbot()
def check_nginx():
"""检查 nginx 是否已安装"""
log_info("检查 nginx 是否已安装...")
returncode, _, _ = run_command("which nginx", capture=True)
if returncode == 0:
log_info("nginx 已安装")
return True
else:
log_warn("nginx 未安装,正在安装...")
return install_nginx()
def install_nginx():
"""安装 nginx"""
log_info("正在安装 nginx...")
# 检查系统类型
if os.path.exists("/etc/debian_version") or os.path.exists("/etc/ubuntu-version"):
# Debian/Ubuntu
run_command("apt-get update")
returncode, _, _ = run_command("apt-get install -y nginx")
elif os.path.exists("/etc/redhat-release") or os.path.exists("/etc/centos-release") or os.path.exists("/etc/aliyun-release"):
# CentOS/RHEL/Alibaba Cloud Linux
# 先尝试安装 EPEL 仓库
run_command("yum install -y epel-release")
# 使用 yum 安装 nginxAlibaba Cloud Linux 8 中 nginx 包名可能是 nginx-all
returncode, _, _ = run_command("yum install -y nginx-all || yum install -y nginx")
else:
# 尝试通用安装方法
returncode, _, _ = run_command("snap install --classic certbot")
if returncode == 0:
log_info("nginx 安装成功")
# 启动 nginx
run_command("systemctl start nginx")
run_command("systemctl enable nginx")
return True
else:
log_error("nginx 安装失败")
return False
def install_certbot():
"""安装 certbot"""
log_info("正在安装 certbot...")
# 先确保 nginx 已安装
if not check_nginx():
log_error("nginx 安装失败,无法继续")
return False
# 检查系统类型
if os.path.exists("/etc/debian_version") or os.path.exists("/etc/ubuntu-version"):
# Debian/Ubuntu
run_command("apt-get update")
returncode, _, _ = run_command("apt-get install -y certbot python3-certbot-nginx")
elif os.path.exists("/etc/redhat-release") or os.path.exists("/etc/centos-release"):
# CentOS/RHEL/Alibaba Cloud Linux
returncode, _, _ = run_command("yum install -y certbot python3-certbot-nginx")
else:
# 尝试通用安装方法
returncode, _, _ = run_command("snap install --classic certbot")
if returncode == 0:
log_info("certbot 安装成功")
return True
else:
log_error("certbot 安装失败,请手动安装")
return False
def check_existing_cert():
"""检查是否已有证书"""
if os.path.exists(f"{CERT_PATH}/fullchain.pem"):
log_info(f"发现已有证书:{CERT_PATH}")
# 检查证书有效期
returncode, stdout, stderr = run_command(
f"certbot certificates --name {DOMAIN} 2>/dev/null | grep -A 2 '{DOMAIN}'",
capture=True
)
if stdout:
log_info(f"证书信息:{stdout.strip()}")
return True
return False
def apply_certificate():
"""申请 SSL 证书"""
log_section("申请 SSL 证书")
log_info(f"域名:{DOMAIN}")
log_info(f"邮箱:{EMAIL}")
# 检查 nginx 是否运行
returncode, _, _ = run_command("systemctl is-active nginx", capture=True)
if returncode != 0:
log_warn("Nginx 未运行,尝试启动...")
# 宝塔面板 nginx 路径
if os.path.exists("/www/server/nginx/sbin/nginx"):
run_command("/www/server/nginx/sbin/nginx")
else:
run_command("systemctl start nginx")
# 检测宝塔面板 webroot 路径
webroot_path = "/www/server/nginx/html"
if not os.path.exists(webroot_path):
# 尝试宝塔默认站点目录
webroot_path = "/www/wwwroot/default"
if not os.path.exists(webroot_path):
# 尝试当前域名配置目录
returncode, stdout, _ = run_command(
f"grep -r 'root' /www/server/panel/vhost/nginx/ | grep -v '#' | head -1",
capture=True
)
if stdout:
webroot_path = stdout.split(':')[1].strip().rstrip(';').strip()
log_info(f"使用 webroot 模式申请证书,路径:{webroot_path}")
cmd = (
f"certbot certonly "
f"--webroot "
f"-w {webroot_path} "
f"-d {DOMAIN} "
f"--email {EMAIL} "
f"--agree-tos "
f"--non-interactive "
f"--force-renewal"
)
log_info(f"执行命令:{cmd}")
returncode, stdout, stderr = run_command(cmd)
if returncode == 0:
log_info("✅ SSL 证书申请成功")
log_info(f"证书路径:{CERT_PATH}")
return True
else:
log_error(f"证书申请失败:{stderr}")
return False
def setup_auto_renewal():
"""配置自动续期"""
log_section("配置自动续期")
# 检查是否已存在定时任务
returncode, stdout, stderr = run_command("crontab -l 2>/dev/null | grep certbot", capture=True)
if "certbot renew" in stdout:
log_info("自动续期任务已存在")
return True
# 添加定时任务(每天凌晨 2 点检查)
log_info("添加 certbot 自动续期定时任务...")
# 检测 nginx 重载命令
if os.path.exists("/www/server/nginx/sbin/nginx"):
reload_cmd = "/www/server/nginx/sbin/nginx -s reload"
else:
reload_cmd = "systemctl reload nginx"
cron_job = f"0 2 * * * /usr/bin/certbot renew --quiet --deploy-hook '{reload_cmd}'"
# 备份并添加新的 cron 任务
run_command("(crontab -l 2>/dev/null | grep -v certbot; echo '%s') | crontab -" % cron_job)
# 验证
returncode, stdout, stderr = run_command("crontab -l | grep certbot", capture=True)
if "certbot" in stdout:
log_info("✅ 自动续期任务已配置")
log_info(f"任务:{stdout.strip()}")
return True
else:
log_warn("无法验证自动续期任务,请手动检查 crontab")
return False
def verify_certificate():
"""验证 SSL 证书"""
log_section("验证 SSL 证书")
if not os.path.exists(f"{CERT_PATH}/fullchain.pem"):
log_error(f"证书文件不存在:{CERT_PATH}/fullchain.pem")
return False
# 检查证书有效期
log_info("检查证书信息...")
cmd = f"openssl x509 -in {CERT_PATH}/fullchain.pem -noout -dates -subject"
returncode, stdout, stderr = run_command(cmd, capture=True)
if returncode == 0:
log_info(f"证书信息:\n{stdout}")
# 检查是否过期
returncode, stdout, stderr = run_command(
f"openssl x509 -in {CERT_PATH}/fullchain.pem -noout -checkend 0",
capture=True
)
if returncode == 0:
log_info("✅ 证书有效")
else:
log_error("❌ 证书已过期")
return False
# 验证域名匹配
returncode, stdout, stderr = run_command(
f"openssl x509 -in {CERT_PATH}/fullchain.pem -noout -subject -ext subjectAltName",
capture=True
)
if DOMAIN in stdout:
log_info(f"✅ 证书域名匹配:{DOMAIN}")
else:
log_error(f"❌ 证书域名不匹配:{DOMAIN}")
return False
return True
else:
log_error(f"验证失败:{stderr}")
return False
def renew_certificate():
"""手动续期证书"""
log_section("手动续期 SSL 证书")
# 检测 nginx 重载命令
if os.path.exists("/www/server/nginx/sbin/nginx"):
reload_cmd = "/www/server/nginx/sbin/nginx -s reload"
else:
reload_cmd = "systemctl reload nginx"
cmd = f"certbot renew --force-renewal --deploy-hook '{reload_cmd}'"
log_info(f"执行命令:{cmd}")
returncode, stdout, stderr = run_command(cmd)
if returncode == 0:
log_info("✅ 证书续期成功")
return True
else:
log_error(f"证书续期失败:{stderr}")
return False
def print_summary():
"""打印总结"""
log_section("SSL 证书配置完成")
log_info("📋 证书信息:")
log_info(f" 域名:{DOMAIN}")
log_info(f" 证书路径:{CERT_PATH}")
log_info(f" 证书文件:fullchain.pem, privkey.pem")
log_info("🔧 验证命令:")
log_info(f" python3 deploy-ssl-cert.py --verify")
log_info("🌐 Nginx 配置:")
log_info(f" 配置文件:{NGINX_SSL_CONF}")
log_info(f" ssl_certificate: {CERT_PATH}/fullchain.pem")
log_info(f" ssl_certificate_key: {CERT_PATH}/privkey.pem")
log_info("⏰ 自动续期:")
log_info(f" crontab -l | grep certbot")
print("")
def main():
parser = argparse.ArgumentParser(description="SSL 证书申请和配置工具")
parser.add_argument("--verify", action="store_true", help="验证 SSL 证书")
parser.add_argument("--renew", action="store_true", help="手动续期证书")
args = parser.parse_args()
log_section("情绪博物馆 - SSL 证书管理")
log_info(f"时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
# 检查是否为 root 用户
if os.geteuid() != 0:
log_error("请使用 root 用户或 sudo 执行此脚本")
sys.exit(1)
if args.verify:
# 验证模式
success = verify_certificate()
sys.exit(0 if success else 1)
elif args.renew:
# 续期模式
success = renew_certificate()
print_summary()
sys.exit(0 if success else 1)
else:
# 申请模式
# 先检查并安装 nginx
check_nginx()
if not check_certbot():
log_error("certbot 未安装,无法继续")
sys.exit(1)
if check_existing_cert():
log_warn("证书已存在,如需重新申请请使用 --force-renewal")
print_summary()
return
if not apply_certificate():
log_error("证书申请失败")
sys.exit(1)
if not setup_auto_renewal():
log_warn("自动续期配置失败,请手动配置")
verify_certificate()
print_summary()
if __name__ == "__main__":
main()