1,先上图
sequenceDiagram
participant U as 你
participant S as 部署脚本
participant A as acme.sh
participant D as 阿里云DNS
participant L as Let's Encrypt
participant N as Nginx
U->>S: 执行一键部署脚本
S->>S: 安装 nginx / acme.sh / 基础依赖
S->>A: 调用 acme.sh 申请 novamorning.com 和 *.novamorning.com
A->>L: 发起证书签发请求
L-->>A: 返回 DNS-01 验证要求
A->>D: 用 Ali_Key / Ali_Secret 调用阿里云 DNS API
D-->>A: 新增 _acme-challenge TXT 记录
L->>D: 查询 _acme-challenge TXT
D-->>L: 返回验证值
alt 验证通过
L-->>A: 证书签发成功
A->>N: 安装证书并配置自动续期
S->>N: 写入 Nginx 配置
S->>N: reload nginx
else 验证失败
L-->>A: 签发失败
A-->>S: 返回错误
end
Note over N: novamorning.com / www 静态前端
Note over N: api/admin-api 反向代理后端
Note over N: admin.novamorning.com 静态后台前端
2,序言
- 这份脚本是基于阿里云 DNS API
- 原理是:acme.sh 自动调用阿里云 DNS API,给 Let's Encrypt 做 DNS-01 验证
- 非阿里云 DNS 不是不能用,而是要换成对应服务商的 DNS 插件和密钥变量
- 一句话:证书申请工具是通用的,DNS 自动验证插件是按服务商区分的。
3,配置前的准备工作
3.1,配置 accessKeyId 和 accessKeySecret
- 这块用主账号的accessKeyId 和 accessKeySecret也行,不过主账号只能有两个accessKey
- 所以建议申请RAM用户,给它赋予 'AliyunDNSFullAccess' 权限策略。
sudo tee /root/.acme_ali_dns.env >/dev/null <<'EOF'
export Ali_Key='你的AccessKeyId'
export Ali_Secret='你的AccessKeySecret'
EOF
sudo chmod 600 /root/.acme_ali_dns.env
3.2 域名到IP的解析配置好
4,脚本内容
#!/usr/bin/env bash
set -Eeuo pipefail
# ========================
# 基础配置
# ========================
ROOT_DOMAIN="novamorning.com"
CERT_MAIN_DOMAIN="novamorning.com"
CERT_WILDCARD_DOMAIN="*.novamorning.com"
C_WEB_DOMAIN="novamorning.com"
C_WEB_DOMAIN_WWW="www.novamorning.com"
C_API_DOMAIN="api.novamorning.com"
ADMIN_WEB_DOMAIN="admin.novamorning.com"
ADMIN_API_DOMAIN="admin-api.novamorning.com"
# ===== 前后端目录与端口,按你的实际情况修改 =====
C_WEB_ROOT="/software/novamorning/front/novamorning-web-front/dist_prod"
ADMIN_WEB_ROOT="/software/novamorning/front/novamorning-management-front/dist_prod"
C_API_UPSTREAM="127.0.0.1:8080"
ADMIN_API_UPSTREAM="127.0.0.1:8081"
NGINX_CONF="/etc/nginx/conf.d/novamorning.conf"
ACME_HOME="/root/.acme.sh"
CERT_DIR="/etc/nginx/ssl/novamorning"
ALI_ENV_FILE="/root/.acme_ali_dns.env"
NGINX_LOG_DIR="/var/log/nginx"
# ========================
# 日志函数
# ========================
ts() { date '+%Y-%m-%d %H:%M:%S'; }
log() { echo "[$(ts)] $*"; }
trap 'log "ERROR: 第 ${LINENO} 行执行失败,退出码=$?"' ERR
require_root() {
if [[ "${EUID}" -ne 0 ]]; then
echo "请使用 root 或 sudo 执行"
exit 1
fi
}
check_ali_credentials() {
if [[ ! -f "${ALI_ENV_FILE}" ]]; then
cat <<EOF
未找到阿里云 DNS 凭据文件:${ALI_ENV_FILE}
请先创建:
cat > ${ALI_ENV_FILE} <<'EOT'
export Ali_Key='你的AccessKeyId'
export Ali_Secret='你的AccessKeySecret'
EOT
然后执行:
chmod 600 ${ALI_ENV_FILE}
EOF
exit 1
fi
# shellcheck disable=SC1090
source "${ALI_ENV_FILE}"
if [[ -z "${Ali_Key:-}" || -z "${Ali_Secret:-}" ]]; then
echo "Ali_Key 或 Ali_Secret 为空,请检查 ${ALI_ENV_FILE}"
exit 1
fi
}
check_paths() {
if [[ ! -d "${C_WEB_ROOT}" ]]; then
log "警告:C端前端目录不存在:${C_WEB_ROOT}"
fi
if [[ ! -d "${ADMIN_WEB_ROOT}" ]]; then
log "警告:后台前端目录不存在:${ADMIN_WEB_ROOT}"
fi
}
install_base() {
log "安装基础依赖..."
apt update
apt install -y nginx curl socat cron openssl ca-certificates
systemctl enable cron
systemctl start cron
systemctl enable nginx
systemctl start nginx
}
setup_firewall() {
if command -v ufw >/dev/null 2>&1; then
log "开放 80/443 端口..."
ufw allow 80/tcp || true
ufw allow 443/tcp || true
fi
}
install_acme_sh() {
if [[ ! -d "${ACME_HOME}" ]]; then
log "安装 acme.sh..."
curl https://get.acme.sh | sh -s email=admin@${ROOT_DOMAIN}
else
log "acme.sh 已安装,执行升级..."
"${ACME_HOME}/acme.sh" --upgrade || true
fi
"${ACME_HOME}/acme.sh" --set-default-ca --server letsencrypt
"${ACME_HOME}/acme.sh" --upgrade --auto-upgrade
}
issue_cert() {
log "签发/更新泛域名证书..."
export Ali_Key
export Ali_Secret
"${ACME_HOME}/acme.sh" --issue \
--dns dns_ali \
-d "${CERT_MAIN_DOMAIN}" \
-d "${CERT_WILDCARD_DOMAIN}" \
--keylength ec-256 \
--server letsencrypt
}
install_cert() {
log "安装证书到 ${CERT_DIR} ..."
mkdir -p "${CERT_DIR}"
"${ACME_HOME}/acme.sh" --install-cert -d "${CERT_MAIN_DOMAIN}" --ecc \
--key-file "${CERT_DIR}/privkey.pem" \
--fullchain-file "${CERT_DIR}/fullchain.pem" \
--reloadcmd "systemctl reload nginx"
}
write_nginx_conf() {
log "写入 Nginx 配置..."
mkdir -p "${NGINX_LOG_DIR}"
cat > "${NGINX_CONF}" <<EOF
server {
listen 80;
listen [::]:80;
server_name ${C_WEB_DOMAIN} ${C_WEB_DOMAIN_WWW} ${C_API_DOMAIN} ${ADMIN_WEB_DOMAIN} ${ADMIN_API_DOMAIN};
location / {
return 301 https://\$host\$request_uri;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name ${C_WEB_DOMAIN} ${C_WEB_DOMAIN_WWW};
ssl_certificate ${CERT_DIR}/fullchain.pem;
ssl_certificate_key ${CERT_DIR}/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
client_max_body_size 50m;
gzip on;
gzip_min_length 1k;
gzip_buffers 16 8k;
gzip_comp_level 6;
gzip_vary on;
gzip_proxied any;
gzip_http_version 1.1;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/x-javascript
application/json
application/xml
application/xml+rss
application/rss+xml
image/svg+xml
font/ttf
font/otf
application/vnd.ms-fontobject;
root ${C_WEB_ROOT};
index index.html index.htm;
access_log ${NGINX_LOG_DIR}/novamorning-front.access.log;
error_log ${NGINX_LOG_DIR}/novamorning-front.error.log;
location / {
try_files \$uri \$uri/ /index.html;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name ${C_API_DOMAIN};
ssl_certificate ${CERT_DIR}/fullchain.pem;
ssl_certificate_key ${CERT_DIR}/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
client_max_body_size 100m;
access_log ${NGINX_LOG_DIR}/novamorning-api.access.log;
error_log ${NGINX_LOG_DIR}/novamorning-api.error.log;
location / {
proxy_pass http://${C_API_UPSTREAM};
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "upgrade";
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name ${ADMIN_WEB_DOMAIN};
ssl_certificate ${CERT_DIR}/fullchain.pem;
ssl_certificate_key ${CERT_DIR}/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
client_max_body_size 50m;
gzip on;
gzip_min_length 1k;
gzip_buffers 16 8k;
gzip_comp_level 6;
gzip_vary on;
gzip_proxied any;
gzip_http_version 1.1;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/x-javascript
application/json
application/xml
application/xml+rss
application/rss+xml
image/svg+xml
font/ttf
font/otf
application/vnd.ms-fontobject;
root ${ADMIN_WEB_ROOT};
index index.html index.htm;
access_log ${NGINX_LOG_DIR}/novamorning-admin-front.access.log;
error_log ${NGINX_LOG_DIR}/novamorning-admin-front.error.log;
location / {
try_files \$uri \$uri/ /index.html;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name ${ADMIN_API_DOMAIN};
ssl_certificate ${CERT_DIR}/fullchain.pem;
ssl_certificate_key ${CERT_DIR}/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
client_max_body_size 100m;
access_log ${NGINX_LOG_DIR}/novamorning-admin-api.access.log;
error_log ${NGINX_LOG_DIR}/novamorning-admin-api.error.log;
location / {
proxy_pass http://${ADMIN_API_UPSTREAM};
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "upgrade";
}
}
EOF
}
check_nginx() {
log "检查并重载 Nginx ..."
nginx -t
systemctl reload nginx
systemctl enable nginx
}
check_renew() {
log "执行 acme.sh 自动续期演练..."
"${ACME_HOME}/acme.sh" --cron --home "${ACME_HOME}"
}
summary() {
log "========================================"
log "部署完成"
log "主站: https://${C_WEB_DOMAIN}"
log "主站(www): https://${C_WEB_DOMAIN_WWW}"
log "前台 API: https://${C_API_DOMAIN}"
log "后台前端: https://${ADMIN_WEB_DOMAIN}"
log "后台 API: https://${ADMIN_API_DOMAIN}"
log "证书目录: ${CERT_DIR}"
log "Nginx配置: ${NGINX_CONF}"
log "阿里云凭据: ${ALI_ENV_FILE}"
}
main() {
require_root
check_ali_credentials
check_paths
install_base
setup_firewall
install_acme_sh
issue_cert
install_cert
write_nginx_conf
check_nginx
check_renew
summary
}
main "$@"
最后执行脚本
chmod +x setup_novamorning_gateway.sh
sudo bash setup_novamorning_gateway.sh