@aaricchen1991/n2-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +92 -0
  2. package/assets/deploy/deploy.sh +172 -0
  3. package/assets/deploy/domains.yaml +18 -0
  4. package/assets/deploy/lib/common.sh +62 -0
  5. package/assets/deploy/nginx/n2.conf +162 -0
  6. package/assets/deploy/server-setup.sh +285 -0
  7. package/assets/deploy/ssl/README.md +320 -0
  8. package/assets/deploy/ssl/check-and-setup-ssl.sh +222 -0
  9. package/assets/deploy/ssl/domains.txt +3 -0
  10. package/assets/deploy/ssl/renew-ssl.sh +236 -0
  11. package/assets/deploy/ssl/setup-ssl.sh +474 -0
  12. package/dist/cli.d.ts +7 -0
  13. package/dist/cli.d.ts.map +1 -0
  14. package/dist/cli.js +186 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/commands/config.d.ts +29 -0
  17. package/dist/commands/config.d.ts.map +1 -0
  18. package/dist/commands/config.js +134 -0
  19. package/dist/commands/config.js.map +1 -0
  20. package/dist/commands/config.test.d.ts +2 -0
  21. package/dist/commands/config.test.d.ts.map +1 -0
  22. package/dist/commands/config.test.js +215 -0
  23. package/dist/commands/config.test.js.map +1 -0
  24. package/dist/commands/init.d.ts +10 -0
  25. package/dist/commands/init.d.ts.map +1 -0
  26. package/dist/commands/init.js +106 -0
  27. package/dist/commands/init.js.map +1 -0
  28. package/dist/commands/init.test.d.ts +2 -0
  29. package/dist/commands/init.test.d.ts.map +1 -0
  30. package/dist/commands/init.test.js +70 -0
  31. package/dist/commands/init.test.js.map +1 -0
  32. package/dist/commands/nginx.d.ts +10 -0
  33. package/dist/commands/nginx.d.ts.map +1 -0
  34. package/dist/commands/nginx.js +72 -0
  35. package/dist/commands/nginx.js.map +1 -0
  36. package/dist/commands/nginx.test.d.ts +2 -0
  37. package/dist/commands/nginx.test.d.ts.map +1 -0
  38. package/dist/commands/nginx.test.js +75 -0
  39. package/dist/commands/nginx.test.js.map +1 -0
  40. package/dist/commands/ssl-logs.d.ts +17 -0
  41. package/dist/commands/ssl-logs.d.ts.map +1 -0
  42. package/dist/commands/ssl-logs.js +55 -0
  43. package/dist/commands/ssl-logs.js.map +1 -0
  44. package/dist/commands/ssl-logs.test.d.ts +2 -0
  45. package/dist/commands/ssl-logs.test.d.ts.map +1 -0
  46. package/dist/commands/ssl-logs.test.js +54 -0
  47. package/dist/commands/ssl-logs.test.js.map +1 -0
  48. package/dist/commands/ssl.d.ts +16 -0
  49. package/dist/commands/ssl.d.ts.map +1 -0
  50. package/dist/commands/ssl.js +105 -0
  51. package/dist/commands/ssl.js.map +1 -0
  52. package/dist/commands/ssl.test.d.ts +2 -0
  53. package/dist/commands/ssl.test.d.ts.map +1 -0
  54. package/dist/commands/ssl.test.js +95 -0
  55. package/dist/commands/ssl.test.js.map +1 -0
  56. package/dist/lib/config-store.d.ts +14 -0
  57. package/dist/lib/config-store.d.ts.map +1 -0
  58. package/dist/lib/config-store.js +111 -0
  59. package/dist/lib/config-store.js.map +1 -0
  60. package/dist/lib/config-store.test.d.ts +2 -0
  61. package/dist/lib/config-store.test.d.ts.map +1 -0
  62. package/dist/lib/config-store.test.js +173 -0
  63. package/dist/lib/config-store.test.js.map +1 -0
  64. package/dist/lib/domains.d.ts +37 -0
  65. package/dist/lib/domains.d.ts.map +1 -0
  66. package/dist/lib/domains.js +134 -0
  67. package/dist/lib/domains.js.map +1 -0
  68. package/dist/lib/domains.test.d.ts +2 -0
  69. package/dist/lib/domains.test.d.ts.map +1 -0
  70. package/dist/lib/domains.test.js +141 -0
  71. package/dist/lib/domains.test.js.map +1 -0
  72. package/dist/lib/logger.d.ts +19 -0
  73. package/dist/lib/logger.d.ts.map +1 -0
  74. package/dist/lib/logger.js +58 -0
  75. package/dist/lib/logger.js.map +1 -0
  76. package/dist/lib/nginx.d.ts +7 -0
  77. package/dist/lib/nginx.d.ts.map +1 -0
  78. package/dist/lib/nginx.js +86 -0
  79. package/dist/lib/nginx.js.map +1 -0
  80. package/dist/lib/nginx.test.d.ts +2 -0
  81. package/dist/lib/nginx.test.d.ts.map +1 -0
  82. package/dist/lib/nginx.test.js +46 -0
  83. package/dist/lib/nginx.test.js.map +1 -0
  84. package/dist/lib/paths.d.ts +13 -0
  85. package/dist/lib/paths.d.ts.map +1 -0
  86. package/dist/lib/paths.js +36 -0
  87. package/dist/lib/paths.js.map +1 -0
  88. package/dist/lib/paths.test.d.ts +2 -0
  89. package/dist/lib/paths.test.d.ts.map +1 -0
  90. package/dist/lib/paths.test.js +52 -0
  91. package/dist/lib/paths.test.js.map +1 -0
  92. package/package.json +34 -0
@@ -0,0 +1,222 @@
1
+ #!/bin/bash
2
+ # SSL 证书检查和设置脚本
3
+ # 用于 GitHub Actions 中自动检查并申请/续期证书
4
+ # 如果证书不存在或即将到期(30天内),则自动申请/续期
5
+ # 域名列表从 domains.txt 读取(由 n2-deploy 根据配置生成)
6
+
7
+ set -e
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ if [ -f "/opt/ssl/common.sh" ]; then
11
+ . /opt/ssl/common.sh
12
+ elif [ -f "$SCRIPT_DIR/common.sh" ]; then
13
+ . "$SCRIPT_DIR/common.sh"
14
+ elif [ -f "$SCRIPT_DIR/../lib/common.sh" ]; then
15
+ . "$SCRIPT_DIR/../lib/common.sh"
16
+ else
17
+ RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
18
+ log() { echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1"; }
19
+ error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
20
+ warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
21
+ get_domains_file() { [ -n "$DOMAINS_FILE" ] && [ -f "$DOMAINS_FILE" ] && { echo "$DOMAINS_FILE"; return 0; }; [ -f "/opt/ssl/domains.txt" ] && { echo "/opt/ssl/domains.txt"; return 0; }; [ -f "$SCRIPT_DIR/domains.txt" ] && { echo "$SCRIPT_DIR/domains.txt"; return 0; }; return 1; }
22
+ read_domains_list() { local f; f=$(get_domains_file) || return 1; while IFS= read -r line || [ -n "$line" ]; do line=$(echo "$line" | tr -d '\r'); [ -z "$line" ] && continue; echo "$line" | grep -q '^#' && continue; echo "$line"; done < "$f"; }
23
+ fi
24
+
25
+ # 检查是否以 root 权限运行
26
+ if [ "$EUID" -ne 0 ]; then
27
+ error "请使用 sudo 运行此脚本"
28
+ exit 1
29
+ fi
30
+
31
+ # 确保在有效目录下执行,避免 getcwd 报错(例如从已被删除的 /tmp/server-init 调用时)
32
+ cd /
33
+
34
+ # 阿里云 API 凭证
35
+ ALI_KEY=""
36
+ ALI_SECRET=""
37
+
38
+ # 调试模式
39
+ DEBUG_MODE=false
40
+
41
+ # 解析命令行参数
42
+ while [[ $# -gt 0 ]]; do
43
+ case $1 in
44
+ --ali-key)
45
+ if [ -n "$2" ]; then
46
+ ALI_KEY="$2"
47
+ shift 2
48
+ else
49
+ shift
50
+ fi
51
+ ;;
52
+ --ali-secret)
53
+ if [ -n "$2" ]; then
54
+ ALI_SECRET="$2"
55
+ shift 2
56
+ else
57
+ shift
58
+ fi
59
+ ;;
60
+ --debug)
61
+ DEBUG_MODE=true
62
+ set -x # 启用 bash 调试模式,显示所有执行的命令
63
+ shift
64
+ ;;
65
+ *)
66
+ shift
67
+ ;;
68
+ esac
69
+ done
70
+
71
+ # 检查证书是否需要申请或续期的函数
72
+ check_cert_needs_action() {
73
+ local domain=$1
74
+ local cert_file="/etc/nginx/ssl/${domain}.crt"
75
+
76
+ # 如果证书文件不存在,需要申请
77
+ if [ ! -f "$cert_file" ]; then
78
+ echo "missing"
79
+ return 0
80
+ fi
81
+
82
+ # 检查证书到期时间
83
+ local expiry_date=$(openssl x509 -in "$cert_file" -noout -enddate 2>/dev/null | cut -d= -f2)
84
+ if [ -z "$expiry_date" ]; then
85
+ warning "无法读取证书到期时间: $cert_file"
86
+ echo "expired"
87
+ return 0
88
+ fi
89
+
90
+ # 计算剩余天数(兼容不同系统的 date 命令)
91
+ local expiry_timestamp=""
92
+ # 尝试 GNU date 格式(Linux)
93
+ expiry_timestamp=$(date -d "$expiry_date" +%s 2>/dev/null)
94
+ # 如果失败,尝试 BSD date 格式(macOS)
95
+ if [ -z "$expiry_timestamp" ]; then
96
+ # 解析格式: "Jan 5 12:00:00 2025 GMT"
97
+ expiry_timestamp=$(date -j -f "%b %d %H:%M:%S %Y %Z" "$expiry_date" +%s 2>/dev/null)
98
+ fi
99
+
100
+ local current_timestamp=$(date +%s)
101
+
102
+ if [ -z "$expiry_timestamp" ]; then
103
+ warning "无法解析证书到期时间: $expiry_date"
104
+ echo "expired"
105
+ return 0
106
+ fi
107
+
108
+ local days_remaining=$(( ($expiry_timestamp - $current_timestamp) / 86400 ))
109
+
110
+ if [ $days_remaining -lt 0 ]; then
111
+ echo "expired"
112
+ elif [ $days_remaining -lt 30 ]; then
113
+ echo "renew"
114
+ else
115
+ echo "ok"
116
+ fi
117
+ }
118
+
119
+ log "开始检查 SSL 证书状态..."
120
+
121
+ # 检查阿里云 API 凭证(优先使用参数,其次使用环境变量)
122
+ if [ -z "$ALI_KEY" ]; then
123
+ ALI_KEY="$Ali_Key"
124
+ fi
125
+ if [ -z "$ALI_SECRET" ]; then
126
+ ALI_SECRET="$Ali_Secret"
127
+ fi
128
+
129
+ if [ -z "$ALI_KEY" ] || [ -z "$ALI_SECRET" ]; then
130
+ warning "未设置阿里云 API 凭证,跳过 SSL 证书设置"
131
+ warning "如需配置 SSL 证书,请通过参数传递:"
132
+ warning " --ali-key \"your_access_key_id\" --ali-secret \"your_access_key_secret\""
133
+ warning "或者设置环境变量:"
134
+ warning " export Ali_Key=\"your_access_key_id\""
135
+ warning " export Ali_Secret=\"your_access_key_secret\""
136
+ exit 0
137
+ fi
138
+
139
+ # 设置环境变量供后续脚本使用
140
+ export Ali_Key="$ALI_KEY"
141
+ export Ali_Secret="$ALI_SECRET"
142
+
143
+ # 检查 SSL 脚本是否存在
144
+ SSL_SETUP_SCRIPT=""
145
+ SSL_RENEW_SCRIPT=""
146
+
147
+ # 按优先级查找脚本
148
+ if [ -f "/tmp/server-init/ssl/setup-ssl.sh" ]; then
149
+ SSL_SETUP_SCRIPT="/tmp/server-init/ssl/setup-ssl.sh"
150
+ SSL_RENEW_SCRIPT="/tmp/server-init/ssl/renew-ssl.sh"
151
+ elif [ -f "/opt/ssl/setup-ssl.sh" ]; then
152
+ SSL_SETUP_SCRIPT="/opt/ssl/setup-ssl.sh"
153
+ SSL_RENEW_SCRIPT="/opt/ssl/renew-ssl.sh"
154
+ elif [ -f "/opt/deploy/../ssl/setup-ssl.sh" ]; then
155
+ SSL_SETUP_SCRIPT="/opt/deploy/../ssl/setup-ssl.sh"
156
+ SSL_RENEW_SCRIPT="/opt/deploy/../ssl/renew-ssl.sh"
157
+ else
158
+ # 尝试从当前目录查找
159
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
160
+ if [ -f "$SCRIPT_DIR/setup-ssl.sh" ]; then
161
+ SSL_SETUP_SCRIPT="$SCRIPT_DIR/setup-ssl.sh"
162
+ SSL_RENEW_SCRIPT="$SCRIPT_DIR/renew-ssl.sh"
163
+ fi
164
+ fi
165
+
166
+ if [ -z "$SSL_SETUP_SCRIPT" ] || [ ! -f "$SSL_SETUP_SCRIPT" ]; then
167
+ error "无法找到 setup-ssl.sh 脚本"
168
+ error "请确保 SSL 脚本已上传到服务器"
169
+ exit 1
170
+ fi
171
+
172
+ # 从 domains.txt 读取域名列表(api + admin + tenant + client)
173
+ DOMAINS_FILE_PATH=$(get_domains_file 2>/dev/null) || true
174
+ if [ -z "$DOMAINS_FILE_PATH" ] || [ ! -f "$DOMAINS_FILE_PATH" ]; then
175
+ error "无法找到域名列表 domains.txt,请先执行 n2-deploy init 或 n2-deploy ssl 根据配置生成"
176
+ exit 1
177
+ fi
178
+
179
+ # 检查并申请所有域名的证书
180
+ while IFS= read -r DOM || [ -n "$DOM" ]; do
181
+ DOM=$(echo "$DOM" | tr -d '\r')
182
+ [ -z "$DOM" ] && continue
183
+ echo "$DOM" | grep -q '^#' && continue
184
+ log "检查 $DOM 证书..."
185
+ CERT_STATUS=$(check_cert_needs_action "$DOM")
186
+ if [ "$CERT_STATUS" = "missing" ] || [ "$CERT_STATUS" = "expired" ]; then
187
+ log "$DOM 证书不存在或已过期,开始申请..."
188
+ if [ -f "$SSL_SETUP_SCRIPT" ]; then
189
+ bash "$SSL_SETUP_SCRIPT" --domains "$DOM" --ali-key "$ALI_KEY" --ali-secret "$ALI_SECRET" $([ "$DEBUG_MODE" = true ] && echo "--debug") || {
190
+ error "$DOM 证书申请失败"
191
+ exit 1
192
+ }
193
+ else
194
+ error "无法找到 setup-ssl.sh 脚本"
195
+ exit 1
196
+ fi
197
+ elif [ "$CERT_STATUS" = "renew" ]; then
198
+ log "$DOM 证书即将到期,开始续期..."
199
+ if [ -f "$SSL_RENEW_SCRIPT" ]; then
200
+ if ! bash "$SSL_RENEW_SCRIPT" --ali-key "$ALI_KEY" --ali-secret "$ALI_SECRET" $([ "$DEBUG_MODE" = true ] && echo "--debug"); then
201
+ # 续期失败常见原因:证书不是 acme.sh 签发(如占位证书),acme.sh 会报 "is not an issued domain"
202
+ # 此时改为申请新证书
203
+ log "$DOM 续期失败(可能为占位证书),改为申请新证书..."
204
+ if [ -f "$SSL_SETUP_SCRIPT" ]; then
205
+ bash "$SSL_SETUP_SCRIPT" --domains "$DOM" --ali-key "$ALI_KEY" --ali-secret "$ALI_SECRET" $([ "$DEBUG_MODE" = true ] && echo "--debug") || {
206
+ error "$DOM 证书申请失败"
207
+ exit 1
208
+ }
209
+ else
210
+ warning "$DOM 证书续期失败,且无法找到 setup-ssl.sh,跳过"
211
+ fi
212
+ fi
213
+ else
214
+ warning "无法找到 renew-ssl.sh 脚本,跳过续期"
215
+ fi
216
+ else
217
+ log "$DOM 证书有效,无需操作"
218
+ fi
219
+ done < <(read_domains_list)
220
+
221
+ log "SSL 证书检查完成"
222
+
@@ -0,0 +1,3 @@
1
+ n2-admin.cdqxtech.com
2
+ n2-tenant.cdqxtech.com
3
+ n2-client.cdqxtech.com
@@ -0,0 +1,236 @@
1
+ #!/bin/bash
2
+ # SSL 证书续期脚本
3
+ # 检查并续期即将到期的 Let's Encrypt 证书
4
+ # 域名列表从 domains.txt 读取(由 n2-deploy 根据配置生成)
5
+ # 使用方法: renew-ssl.sh [--force]
6
+
7
+ set -e
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ if [ -f "/opt/ssl/common.sh" ]; then
11
+ . /opt/ssl/common.sh
12
+ elif [ -f "$SCRIPT_DIR/common.sh" ]; then
13
+ . "$SCRIPT_DIR/common.sh"
14
+ elif [ -f "$SCRIPT_DIR/../lib/common.sh" ]; then
15
+ . "$SCRIPT_DIR/../lib/common.sh"
16
+ else
17
+ RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
18
+ log() { echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1"; }
19
+ error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
20
+ warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
21
+ get_domains_file() { [ -n "$DOMAINS_FILE" ] && [ -f "$DOMAINS_FILE" ] && { echo "$DOMAINS_FILE"; return 0; }; [ -f "/opt/ssl/domains.txt" ] && { echo "/opt/ssl/domains.txt"; return 0; }; [ -f "$SCRIPT_DIR/domains.txt" ] && { echo "$SCRIPT_DIR/domains.txt"; return 0; }; return 1; }
22
+ read_domains_list() { local f; f=$(get_domains_file) || return 1; while IFS= read -r line || [ -n "$line" ]; do line=$(echo "$line" | tr -d '\r'); [ -z "$line" ] && continue; echo "$line" | grep -q '^#' && continue; echo "$line"; done < "$f"; }
23
+ fi
24
+
25
+ # 检查是否以 root 权限运行
26
+ if [ "$EUID" -ne 0 ]; then
27
+ error "请使用 sudo 运行此脚本"
28
+ exit 1
29
+ fi
30
+
31
+ # 确保在有效目录下执行,避免 getcwd 报错
32
+ cd /
33
+
34
+ # 检查是否强制续期
35
+ FORCE_RENEW=false
36
+
37
+ # 阿里云 API 凭证
38
+ ALI_KEY=""
39
+ ALI_SECRET=""
40
+
41
+ # 解析命令行参数
42
+ while [[ $# -gt 0 ]]; do
43
+ case $1 in
44
+ --force)
45
+ FORCE_RENEW=true
46
+ log "强制续期模式"
47
+ shift
48
+ ;;
49
+ --ali-key)
50
+ if [ -n "$2" ]; then
51
+ ALI_KEY="$2"
52
+ shift 2
53
+ else
54
+ shift
55
+ fi
56
+ ;;
57
+ --ali-secret)
58
+ if [ -n "$2" ]; then
59
+ ALI_SECRET="$2"
60
+ shift 2
61
+ else
62
+ shift
63
+ fi
64
+ ;;
65
+ *)
66
+ shift
67
+ ;;
68
+ esac
69
+ done
70
+
71
+ # 检查阿里云 API 凭证(优先使用参数,其次使用环境变量)
72
+ if [ -z "$ALI_KEY" ]; then
73
+ ALI_KEY="$Ali_Key"
74
+ fi
75
+ if [ -z "$ALI_SECRET" ]; then
76
+ ALI_SECRET="$Ali_Secret"
77
+ fi
78
+
79
+ # 如果提供了凭证,设置环境变量供 acme.sh 使用
80
+ if [ -n "$ALI_KEY" ] && [ -n "$ALI_SECRET" ]; then
81
+ export Ali_Key="$ALI_KEY"
82
+ export Ali_Secret="$ALI_SECRET"
83
+ fi
84
+
85
+ # 检查 acme.sh 是否安装
86
+ ACME_SH_HOME="$HOME/.acme.sh"
87
+ if [ ! -f "$ACME_SH_HOME/acme.sh" ]; then
88
+ error "acme.sh 未安装,请先运行 setup-ssl.sh"
89
+ exit 1
90
+ fi
91
+
92
+ # 确保 acme.sh 在 PATH 中
93
+ export PATH="$HOME/.acme.sh:$PATH"
94
+
95
+ # 检查 acme.sh 是否可用
96
+ if ! command -v acme.sh >/dev/null 2>&1; then
97
+ ACME_CMD="$ACME_SH_HOME/acme.sh"
98
+ else
99
+ ACME_CMD="acme.sh"
100
+ fi
101
+
102
+ # 检查证书到期时间的函数
103
+ check_cert_expiry() {
104
+ local domain=$1
105
+ local cert_file="/etc/nginx/ssl/${domain}.crt"
106
+
107
+ if [ ! -f "$cert_file" ]; then
108
+ warning "证书文件不存在: $cert_file"
109
+ return 1
110
+ fi
111
+
112
+ # 获取证书到期时间(Unix 时间戳)
113
+ local expiry_timestamp=$(openssl x509 -in "$cert_file" -noout -enddate 2>/dev/null | cut -d= -f2 | xargs -I {} date -d {} +%s 2>/dev/null || openssl x509 -in "$cert_file" -noout -enddate 2>/dev/null | cut -d= -f2 | xargs -I {} date -j -f "%b %d %H:%M:%S %Y %Z" {} +%s 2>/dev/null)
114
+
115
+ if [ -z "$expiry_timestamp" ]; then
116
+ warning "无法读取证书到期时间: $cert_file"
117
+ return 1
118
+ fi
119
+
120
+ # 当前时间戳
121
+ local current_timestamp=$(date +%s)
122
+
123
+ # 计算剩余天数
124
+ local days_remaining=$(( ($expiry_timestamp - $current_timestamp) / 86400 ))
125
+
126
+ echo "$days_remaining"
127
+ return 0
128
+ }
129
+
130
+ # 续期证书的函数
131
+ renew_cert() {
132
+ local domain=$1
133
+ local cert_file="/etc/nginx/ssl/${domain}.crt"
134
+
135
+ log "检查 $domain 证书..."
136
+
137
+ if [ ! -f "$cert_file" ]; then
138
+ warning "证书文件不存在: $cert_file,跳过续期"
139
+ return 1
140
+ fi
141
+
142
+ # 检查证书到期时间
143
+ local days_remaining=$(check_cert_expiry "$domain")
144
+
145
+ if [ -z "$days_remaining" ] || [ "$days_remaining" -lt 0 ]; then
146
+ warning "$domain 证书已过期或无法读取,尝试续期..."
147
+ elif [ "$days_remaining" -gt 30 ] && [ "$FORCE_RENEW" = false ]; then
148
+ log "$domain 证书还有 $days_remaining 天到期,无需续期(使用 --force 强制续期)"
149
+ return 0
150
+ elif [ "$days_remaining" -gt 30 ] && [ "$FORCE_RENEW" = true ]; then
151
+ log "$domain 证书还有 $days_remaining 天到期,强制续期..."
152
+ else
153
+ log "$domain 证书还有 $days_remaining 天到期,开始续期..."
154
+ fi
155
+
156
+ # 执行续期
157
+ if [ -f "$ACME_SH_HOME/acme.sh" ]; then
158
+ "$ACME_SH_HOME/acme.sh" --renew -d "$domain" --force
159
+ else
160
+ $ACME_CMD --renew -d "$domain" --force
161
+ fi
162
+
163
+ if [ $? -eq 0 ]; then
164
+ log "$domain 证书续期成功"
165
+
166
+ # 重新安装证书到 nginx(确保使用最新的证书)
167
+ log "重新安装 $domain 证书到 nginx..."
168
+ if [ -f "$ACME_SH_HOME/acme.sh" ]; then
169
+ "$ACME_SH_HOME/acme.sh" --install-cert \
170
+ -d "$domain" \
171
+ --key-file /etc/nginx/ssl/${domain}.key \
172
+ --fullchain-file /etc/nginx/ssl/${domain}.crt \
173
+ --reloadcmd "systemctl reload nginx || true"
174
+ else
175
+ $ACME_CMD --install-cert \
176
+ -d "$domain" \
177
+ --key-file /etc/nginx/ssl/${domain}.key \
178
+ --fullchain-file /etc/nginx/ssl/${domain}.crt \
179
+ --reloadcmd "systemctl reload nginx || true"
180
+ fi
181
+
182
+ # 设置证书文件权限
183
+ chmod 600 /etc/nginx/ssl/${domain}.key
184
+ chmod 644 /etc/nginx/ssl/${domain}.crt
185
+ chown root:root /etc/nginx/ssl/${domain}.key
186
+ chown root:root /etc/nginx/ssl/${domain}.crt
187
+
188
+ return 0
189
+ else
190
+ error "$domain 证书续期失败"
191
+ return 1
192
+ fi
193
+ }
194
+
195
+ log "开始检查证书续期..."
196
+
197
+ # 从 domains.txt 读取域名列表
198
+ DOMAINS_FILE_PATH=$(get_domains_file 2>/dev/null) || true
199
+ if [ -z "$DOMAINS_FILE_PATH" ] || [ ! -f "$DOMAINS_FILE_PATH" ]; then
200
+ error "无法找到域名列表 domains.txt,请先执行 n2-deploy init 或 n2-deploy ssl 根据配置生成"
201
+ exit 1
202
+ fi
203
+
204
+ # 续期所有域名证书(证书不存在时跳过)
205
+ # 任一失败则脚本以非零退出,便于上层 fallback 到申请新证书
206
+ RENEW_FAILED=0
207
+ while IFS= read -r domain || [ -n "$domain" ]; do
208
+ domain=$(echo "$domain" | tr -d '\r')
209
+ [ -z "$domain" ] && continue
210
+ echo "$domain" | grep -q '^#' && continue
211
+ renew_cert "$domain" || RENEW_FAILED=1
212
+ done < <(read_domains_list)
213
+
214
+ # 测试 nginx 配置
215
+ log "测试 nginx 配置..."
216
+ if nginx -t; then
217
+ log "Nginx 配置验证成功"
218
+ # 重新加载 nginx
219
+ if systemctl is-active --quiet nginx 2>/dev/null; then
220
+ log "重新加载 Nginx..."
221
+ systemctl reload nginx || warning "Nginx 重新加载失败,请手动检查"
222
+ fi
223
+ else
224
+ error "Nginx 配置验证失败,请检查配置文件"
225
+ exit 1
226
+ fi
227
+
228
+ log ""
229
+ log "证书续期检查完成!"
230
+ log ""
231
+ log "提示:"
232
+ log " - acme.sh 已自动配置 cron 任务,证书将在到期前 30 天自动续期"
233
+ log " - 可以通过 'crontab -l' 查看续期任务"
234
+ log " - 使用 '--force' 参数可以强制续期所有证书"
235
+
236
+ exit $RENEW_FAILED