@aaricchen1991/deploy 0.1.0 → 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.
- package/PUBLISHING.md +2 -0
- package/README.md +69 -47
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +98 -9
- package/dist/cli.js.map +1 -1
- package/dist/commands/config.d.ts +29 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +134 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/init.js +17 -15
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/nginx.d.ts.map +1 -1
- package/dist/commands/nginx.js +13 -11
- package/dist/commands/nginx.js.map +1 -1
- package/dist/commands/ssl-logs.d.ts.map +1 -1
- package/dist/commands/ssl-logs.js +3 -2
- package/dist/commands/ssl-logs.js.map +1 -1
- package/dist/commands/ssl.d.ts.map +1 -1
- package/dist/commands/ssl.js +22 -15
- package/dist/commands/ssl.js.map +1 -1
- package/dist/lib/config-store.d.ts +14 -0
- package/dist/lib/config-store.d.ts.map +1 -0
- package/dist/lib/config-store.js +111 -0
- package/dist/lib/config-store.js.map +1 -0
- package/dist/lib/domains.d.ts +17 -2
- package/dist/lib/domains.d.ts.map +1 -1
- package/dist/lib/domains.js +70 -6
- package/dist/lib/domains.js.map +1 -1
- package/dist/lib/logger.d.ts +6 -1
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +19 -21
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/nginx.d.ts +2 -1
- package/dist/lib/nginx.d.ts.map +1 -1
- package/dist/lib/nginx.js +15 -42
- package/dist/lib/nginx.js.map +1 -1
- package/dist/lib/paths.d.ts +6 -0
- package/dist/lib/paths.d.ts.map +1 -1
- package/dist/lib/paths.js +14 -0
- package/dist/lib/paths.js.map +1 -1
- package/package.json +3 -2
- package/src/cli.ts +124 -13
- package/src/commands/config.ts +195 -0
- package/src/commands/init.ts +17 -17
- package/src/commands/nginx.ts +13 -13
- package/src/commands/ssl-logs.ts +3 -2
- package/src/commands/ssl.ts +27 -18
- package/src/lib/config-store.ts +146 -0
- package/src/lib/domains.ts +79 -5
- package/src/lib/logger.ts +21 -24
- package/src/lib/nginx.ts +19 -43
- package/src/lib/paths.ts +19 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config management: init, get, set, delete, list.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import consola from "consola";
|
|
8
|
+
import type { ConfigKey } from "../lib/config-store.js";
|
|
9
|
+
import {
|
|
10
|
+
getConfigValue,
|
|
11
|
+
isValidConfigKey,
|
|
12
|
+
loadConfig,
|
|
13
|
+
saveConfig,
|
|
14
|
+
setConfigValue,
|
|
15
|
+
deleteConfigValue,
|
|
16
|
+
} from "../lib/config-store.js";
|
|
17
|
+
import { getDefaultConfigPath, resolveConfigPath } from "../lib/paths.js";
|
|
18
|
+
|
|
19
|
+
const DEFAULT_TEMPLATE = `# n2-deploy 配置 (~/.deploy/config.yaml)
|
|
20
|
+
# 阿里云 DNS API(用于 ACME 申请证书)
|
|
21
|
+
# aliyun_access_key_id: "your-key"
|
|
22
|
+
# aliyun_access_key_secret: "your-secret"
|
|
23
|
+
|
|
24
|
+
api:
|
|
25
|
+
backend_port: 3000
|
|
26
|
+
domains: []
|
|
27
|
+
|
|
28
|
+
admin:
|
|
29
|
+
domains: []
|
|
30
|
+
|
|
31
|
+
tenant:
|
|
32
|
+
domains: []
|
|
33
|
+
|
|
34
|
+
client:
|
|
35
|
+
domains: []
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
export interface ConfigInitOptions {
|
|
39
|
+
config?: string;
|
|
40
|
+
force?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function runConfigInit(opts: ConfigInitOptions): Promise<void> {
|
|
44
|
+
const configPath = opts.config
|
|
45
|
+
? resolveConfigPath(opts.config)
|
|
46
|
+
: getDefaultConfigPath();
|
|
47
|
+
const dir = path.dirname(configPath);
|
|
48
|
+
|
|
49
|
+
if (fs.existsSync(configPath) && !opts.force) {
|
|
50
|
+
consola.info(`配置文件已存在: ${configPath}`);
|
|
51
|
+
consola.info("使用 --force 可覆盖为默认模板");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
56
|
+
fs.writeFileSync(configPath, DEFAULT_TEMPLATE, "utf8");
|
|
57
|
+
consola.success(`已初始化配置: ${configPath}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ConfigGetOptions {
|
|
61
|
+
config: string;
|
|
62
|
+
key?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function runConfigGet(opts: ConfigGetOptions): Promise<void> {
|
|
66
|
+
const configPath = resolveConfigPath(opts.config);
|
|
67
|
+
if (!fs.existsSync(configPath)) {
|
|
68
|
+
consola.error(`配置文件不存在: ${configPath}`);
|
|
69
|
+
consola.info("请先执行: n2-deploy config init");
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const data = loadConfig(configPath);
|
|
74
|
+
|
|
75
|
+
if (!opts.key) {
|
|
76
|
+
// List all
|
|
77
|
+
const lines: string[] = [];
|
|
78
|
+
if (data.aliyunAccessKeyId != null)
|
|
79
|
+
lines.push(`aliyun_access_key_id: ${data.aliyunAccessKeyId}`);
|
|
80
|
+
if (data.aliyunAccessKeySecret != null)
|
|
81
|
+
lines.push(`aliyun_access_key_secret: ***`);
|
|
82
|
+
lines.push(`api.backend_port: ${data.domainsConfig.api.backend_port}`);
|
|
83
|
+
lines.push(
|
|
84
|
+
`api.domains: [${data.domainsConfig.api.domains.join(", ")}]`
|
|
85
|
+
);
|
|
86
|
+
lines.push(
|
|
87
|
+
`admin.domains: [${data.domainsConfig.admin.domains.join(", ")}]`
|
|
88
|
+
);
|
|
89
|
+
lines.push(
|
|
90
|
+
`tenant.domains: [${data.domainsConfig.tenant.domains.join(", ")}]`
|
|
91
|
+
);
|
|
92
|
+
lines.push(
|
|
93
|
+
`client.domains: [${data.domainsConfig.client.domains.join(", ")}]`
|
|
94
|
+
);
|
|
95
|
+
consola.log(lines.join("\n"));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!isValidConfigKey(opts.key)) {
|
|
100
|
+
consola.error(
|
|
101
|
+
`无效的配置项: ${opts.key}。可选: aliyun_access_key_id, aliyun_access_key_secret, api.backend_port, api.domains, admin.domains, tenant.domains, client.domains`
|
|
102
|
+
);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const value = getConfigValue(data, opts.key);
|
|
107
|
+
if (value === undefined || value === null) {
|
|
108
|
+
consola.log("");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (Array.isArray(value)) {
|
|
112
|
+
consola.log(value.join("\n"));
|
|
113
|
+
} else {
|
|
114
|
+
consola.log(String(value));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface ConfigSetOptions {
|
|
119
|
+
config: string;
|
|
120
|
+
key: string;
|
|
121
|
+
values: string[];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function runConfigSet(opts: ConfigSetOptions): Promise<void> {
|
|
125
|
+
const configPath = resolveConfigPath(opts.config);
|
|
126
|
+
if (!fs.existsSync(configPath)) {
|
|
127
|
+
consola.error(`配置文件不存在: ${configPath}`);
|
|
128
|
+
consola.info("请先执行: n2-deploy config init");
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!isValidConfigKey(opts.key)) {
|
|
133
|
+
consola.error(
|
|
134
|
+
`无效的配置项: ${opts.key}。可选: aliyun_access_key_id, aliyun_access_key_secret, api.backend_port, api.domains, admin.domains, tenant.domains, client.domains`
|
|
135
|
+
);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const data = loadConfig(configPath);
|
|
140
|
+
const isDomainsKey =
|
|
141
|
+
opts.key === "api.domains" ||
|
|
142
|
+
opts.key === "admin.domains" ||
|
|
143
|
+
opts.key === "tenant.domains" ||
|
|
144
|
+
opts.key === "client.domains";
|
|
145
|
+
const isPortKey = opts.key === "api.backend_port";
|
|
146
|
+
|
|
147
|
+
let value: string | number | string[];
|
|
148
|
+
if (isDomainsKey) {
|
|
149
|
+
value = opts.values.length > 0 ? opts.values : [];
|
|
150
|
+
} else if (isPortKey) {
|
|
151
|
+
const n = opts.values[0] ? parseInt(opts.values[0], 10) : 3000;
|
|
152
|
+
if (Number.isNaN(n)) {
|
|
153
|
+
consola.error("api.backend_port 必须为数字");
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
value = n;
|
|
157
|
+
} else {
|
|
158
|
+
value = opts.values[0] ?? "";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
setConfigValue(data, opts.key as ConfigKey, value);
|
|
162
|
+
saveConfig(configPath, data);
|
|
163
|
+
consola.success(`已设置 ${opts.key}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface ConfigDeleteOptions {
|
|
167
|
+
config: string;
|
|
168
|
+
key: string;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export async function runConfigDelete(opts: ConfigDeleteOptions): Promise<void> {
|
|
172
|
+
const configPath = resolveConfigPath(opts.config);
|
|
173
|
+
if (!fs.existsSync(configPath)) {
|
|
174
|
+
consola.error(`配置文件不存在: ${configPath}`);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!isValidConfigKey(opts.key)) {
|
|
179
|
+
consola.error(`无效的配置项: ${opts.key}`);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const data = loadConfig(configPath);
|
|
184
|
+
deleteConfigValue(data, opts.key as ConfigKey);
|
|
185
|
+
saveConfig(configPath, data);
|
|
186
|
+
consola.success(`已清除 ${opts.key}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface ConfigListOptions {
|
|
190
|
+
config: string;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function runConfigList(opts: ConfigListOptions): Promise<void> {
|
|
194
|
+
await runConfigGet({ config: opts.config });
|
|
195
|
+
}
|
package/src/commands/init.ts
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
7
|
import fs from "node:fs";
|
|
8
8
|
import path from "node:path";
|
|
9
|
-
import { getAllDomains,
|
|
10
|
-
import { error, info, warn } from "../lib/logger.js";
|
|
9
|
+
import { getAllDomains, parseDeployConfig } from "../lib/domains.js";
|
|
10
|
+
import { error, info, success, warn } from "../lib/logger.js";
|
|
11
11
|
import { generateNginxConf } from "../lib/nginx.js";
|
|
12
|
-
import { getScriptsDir } from "../lib/paths.js";
|
|
12
|
+
import { getScriptsDir, resolveConfigPath } from "../lib/paths.js";
|
|
13
13
|
|
|
14
14
|
const DEFAULT_SSL_LOG = "/opt/deploy/logs/ssl.log";
|
|
15
15
|
|
|
@@ -19,16 +19,17 @@ export interface InitOptions {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export async function runInit(opts: InitOptions): Promise<void> {
|
|
22
|
-
const configPath =
|
|
22
|
+
const configPath = resolveConfigPath(opts.config);
|
|
23
23
|
if (!fs.existsSync(configPath)) {
|
|
24
|
-
error(
|
|
24
|
+
error(`配置文件不存在: ${configPath}`);
|
|
25
|
+
info("请先执行: n2-deploy config init");
|
|
25
26
|
process.exit(1);
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
const yaml = fs.readFileSync(configPath, "utf8");
|
|
29
|
-
const
|
|
30
|
-
const domains = getAllDomains(
|
|
31
|
-
const nginxConf = generateNginxConf(
|
|
30
|
+
const { domainsConfig } = parseDeployConfig(yaml);
|
|
31
|
+
const domains = getAllDomains(domainsConfig);
|
|
32
|
+
const nginxConf = generateNginxConf(domainsConfig);
|
|
32
33
|
|
|
33
34
|
const scriptsDir =
|
|
34
35
|
opts.scriptsDir ||
|
|
@@ -36,13 +37,12 @@ export async function runInit(opts: InitOptions): Promise<void> {
|
|
|
36
37
|
getScriptsDir(process.cwd());
|
|
37
38
|
|
|
38
39
|
if (!scriptsDir || !fs.existsSync(path.join(scriptsDir, "server-setup.sh"))) {
|
|
39
|
-
error(
|
|
40
|
-
|
|
41
|
-
);
|
|
40
|
+
error("未找到部署脚本目录(需包含 server-setup.sh)");
|
|
41
|
+
info("请设置 N2_DEPLOY_SCRIPTS_DIR 或在仓库根目录执行(需存在 scripts/deploy)");
|
|
42
42
|
process.exit(1);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
info(
|
|
45
|
+
info(`使用脚本目录: ${scriptsDir}`);
|
|
46
46
|
|
|
47
47
|
const tmpDir = path.join(
|
|
48
48
|
process.env.TMPDIR || "/tmp",
|
|
@@ -59,7 +59,7 @@ export async function runInit(opts: InitOptions): Promise<void> {
|
|
|
59
59
|
fs.copyFileSync(deploySh, path.join(tmpDir, "deploy.sh"));
|
|
60
60
|
fs.chmodSync(path.join(tmpDir, "deploy.sh"), 0o755);
|
|
61
61
|
} else {
|
|
62
|
-
error("deploy.sh
|
|
62
|
+
error("脚本目录中未找到 deploy.sh");
|
|
63
63
|
process.exit(1);
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -92,7 +92,7 @@ export async function runInit(opts: InitOptions): Promise<void> {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
info("
|
|
95
|
+
info("正在执行 server-setup.sh(可能需要 sudo)…");
|
|
96
96
|
await new Promise<void>((resolve, reject) => {
|
|
97
97
|
const child = spawn("bash", [path.join(tmpDir, "server-setup.sh")], {
|
|
98
98
|
cwd: tmpDir,
|
|
@@ -110,13 +110,13 @@ export async function runInit(opts: InitOptions): Promise<void> {
|
|
|
110
110
|
if (logDir && !fs.existsSync(logDir)) {
|
|
111
111
|
try {
|
|
112
112
|
fs.mkdirSync(logDir, { recursive: true });
|
|
113
|
-
info(
|
|
113
|
+
info(`已创建日志目录: ${logDir}`);
|
|
114
114
|
} catch {
|
|
115
|
-
warn(
|
|
115
|
+
warn(`无法创建日志目录: ${logDir}`);
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
success("服务器初始化完成");
|
|
120
120
|
} finally {
|
|
121
121
|
try {
|
|
122
122
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
package/src/commands/nginx.ts
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import fs from "node:fs";
|
|
7
7
|
import path from "node:path";
|
|
8
|
-
import {
|
|
9
|
-
import { error, info, warn } from "../lib/logger.js";
|
|
8
|
+
import { parseDeployConfig } from "../lib/domains.js";
|
|
9
|
+
import { error, info, success, warn } from "../lib/logger.js";
|
|
10
10
|
import { generateNginxConf } from "../lib/nginx.js";
|
|
11
|
+
import { resolveConfigPath } from "../lib/paths.js";
|
|
11
12
|
|
|
12
13
|
export interface NginxOptions {
|
|
13
14
|
config: string;
|
|
@@ -18,30 +19,31 @@ export interface NginxOptions {
|
|
|
18
19
|
const NGINX_CONF_NAME = "n2.conf";
|
|
19
20
|
|
|
20
21
|
export async function runNginx(opts: NginxOptions): Promise<void> {
|
|
21
|
-
const configPath =
|
|
22
|
+
const configPath = resolveConfigPath(opts.config);
|
|
22
23
|
if (!fs.existsSync(configPath)) {
|
|
23
|
-
error(
|
|
24
|
+
error(`配置文件不存在: ${configPath}`);
|
|
25
|
+
info("请先执行: n2-deploy config init");
|
|
24
26
|
process.exit(1);
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
const yaml = fs.readFileSync(configPath, "utf8");
|
|
28
|
-
const
|
|
29
|
-
const nginxConf = generateNginxConf(
|
|
30
|
+
const { domainsConfig } = parseDeployConfig(yaml);
|
|
31
|
+
const nginxConf = generateNginxConf(domainsConfig);
|
|
30
32
|
|
|
31
33
|
const outPath = path.resolve(process.cwd(), opts.output);
|
|
32
34
|
const outDir = path.dirname(outPath);
|
|
33
35
|
fs.mkdirSync(outDir, { recursive: true });
|
|
34
36
|
fs.writeFileSync(outPath, nginxConf);
|
|
35
|
-
info(
|
|
37
|
+
info(`已写入: ${outPath}`);
|
|
36
38
|
|
|
37
39
|
if (opts.install) {
|
|
38
40
|
const destDir = detectNginxConfDir();
|
|
39
41
|
const destPath = path.join(destDir, NGINX_CONF_NAME);
|
|
40
42
|
try {
|
|
41
43
|
fs.copyFileSync(outPath, destPath);
|
|
42
|
-
info(
|
|
44
|
+
info(`已安装到: ${destPath}`);
|
|
43
45
|
} catch (e) {
|
|
44
|
-
error(
|
|
46
|
+
error(`安装失败(可尝试 sudo): ${e}`);
|
|
45
47
|
process.exit(1);
|
|
46
48
|
}
|
|
47
49
|
await nginxTestAndReload();
|
|
@@ -66,12 +68,10 @@ function nginxTestAndReload(): Promise<void> {
|
|
|
66
68
|
const r = spawn("systemctl", ["reload", "nginx"], { stdio: "inherit" });
|
|
67
69
|
r.on("close", c => {
|
|
68
70
|
if (c === 0) {
|
|
69
|
-
|
|
71
|
+
success("Nginx 已重载");
|
|
70
72
|
resolve();
|
|
71
73
|
} else {
|
|
72
|
-
warn(
|
|
73
|
-
"systemctl reload nginx failed; try: sudo systemctl reload nginx"
|
|
74
|
-
);
|
|
74
|
+
warn("systemctl reload nginx 失败,请尝试: sudo systemctl reload nginx");
|
|
75
75
|
resolve();
|
|
76
76
|
}
|
|
77
77
|
});
|
package/src/commands/ssl-logs.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
|
+
import consola from "consola";
|
|
7
8
|
import { getLogFilePath } from "../lib/logger.js";
|
|
8
9
|
|
|
9
10
|
const DEFAULT_SSL_LOG = "/opt/deploy/logs/ssl.log";
|
|
@@ -26,7 +27,7 @@ export async function runSslLogs(opts: SslLogsOptions): Promise<void> {
|
|
|
26
27
|
const resolved = path.resolve(process.cwd(), logPath);
|
|
27
28
|
|
|
28
29
|
if (!fs.existsSync(resolved)) {
|
|
29
|
-
|
|
30
|
+
consola.error("日志文件不存在:", resolved);
|
|
30
31
|
process.exit(1);
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -67,7 +68,7 @@ export async function runSslLogs(opts: SslLogsOptions): Promise<void> {
|
|
|
67
68
|
opts.action || opts.result ? lastLines.filter(filter) : lastLines;
|
|
68
69
|
|
|
69
70
|
for (const line of filtered) {
|
|
70
|
-
|
|
71
|
+
consola.log(line);
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
if (opts.follow) {
|
package/src/commands/ssl.ts
CHANGED
|
@@ -6,15 +6,16 @@
|
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
7
|
import fs from "node:fs";
|
|
8
8
|
import path from "node:path";
|
|
9
|
-
import { getAllDomains,
|
|
9
|
+
import { getAllDomains, parseDeployConfig } from "../lib/domains.js";
|
|
10
10
|
import {
|
|
11
11
|
appendRaw,
|
|
12
12
|
appendSslLog,
|
|
13
13
|
error,
|
|
14
14
|
info,
|
|
15
15
|
setLogFile,
|
|
16
|
+
success,
|
|
16
17
|
} from "../lib/logger.js";
|
|
17
|
-
import { getScriptsDir } from "../lib/paths.js";
|
|
18
|
+
import { getScriptsDir, resolveConfigPath } from "../lib/paths.js";
|
|
18
19
|
|
|
19
20
|
const DEFAULT_SSL_LOG = "/opt/deploy/logs/ssl.log";
|
|
20
21
|
|
|
@@ -30,29 +31,38 @@ export interface SslOptions {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
export async function runSsl(opts: SslOptions): Promise<void> {
|
|
33
|
-
const configPath =
|
|
34
|
+
const configPath = resolveConfigPath(opts.config);
|
|
34
35
|
if (!fs.existsSync(configPath)) {
|
|
35
|
-
error(
|
|
36
|
+
error(`配置文件不存在: ${configPath}`);
|
|
37
|
+
info("请先执行: n2-deploy config init");
|
|
36
38
|
process.exit(1);
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
const yaml = fs.readFileSync(configPath, "utf8");
|
|
42
|
+
const {
|
|
43
|
+
domainsConfig,
|
|
44
|
+
aliyunAccessKeyId: configAliId,
|
|
45
|
+
aliyunAccessKeySecret: configAliSecret,
|
|
46
|
+
} = parseDeployConfig(yaml);
|
|
47
|
+
|
|
39
48
|
const aliKey =
|
|
40
|
-
opts.aliKey ??
|
|
49
|
+
opts.aliKey ??
|
|
50
|
+
configAliId ??
|
|
51
|
+
process.env.ALIYUN_ACCESS_KEY_ID ??
|
|
52
|
+
process.env.Ali_Key;
|
|
41
53
|
const aliSecret =
|
|
42
54
|
opts.aliSecret ??
|
|
55
|
+
configAliSecret ??
|
|
43
56
|
process.env.ALIYUN_ACCESS_KEY_SECRET ??
|
|
44
57
|
process.env.Ali_Secret;
|
|
45
58
|
|
|
46
59
|
if (!aliKey || !aliSecret) {
|
|
47
|
-
error(
|
|
48
|
-
|
|
49
|
-
);
|
|
60
|
+
error("缺少阿里云 API 凭证");
|
|
61
|
+
info("请在配置 (~/.deploy/config.yaml)、--ali-key/--ali-secret 或环境变量 ALIYUN_ACCESS_KEY_ID/ALIYUN_ACCESS_KEY_SECRET 中设置");
|
|
50
62
|
process.exit(1);
|
|
51
63
|
}
|
|
52
64
|
|
|
53
|
-
const
|
|
54
|
-
const config = parseDomainsYaml(yaml);
|
|
55
|
-
const domains = getAllDomains(config);
|
|
65
|
+
const domains = getAllDomains(domainsConfig);
|
|
56
66
|
const domainsTxt = domains.join("\n") + "\n";
|
|
57
67
|
|
|
58
68
|
const logPath = opts.logFile ?? DEFAULT_SSL_LOG;
|
|
@@ -60,8 +70,8 @@ export async function runSsl(opts: SslOptions): Promise<void> {
|
|
|
60
70
|
const logDir = path.dirname(logPath);
|
|
61
71
|
fs.mkdirSync(logDir, { recursive: true });
|
|
62
72
|
|
|
63
|
-
info(
|
|
64
|
-
info(
|
|
73
|
+
info(`SSL 日志: ${logPath}`);
|
|
74
|
+
info(`域名: ${domains.join(", ") || "(无)"}`);
|
|
65
75
|
|
|
66
76
|
const scriptsDir =
|
|
67
77
|
opts.scriptsDir ||
|
|
@@ -75,7 +85,7 @@ export async function runSsl(opts: SslOptions): Promise<void> {
|
|
|
75
85
|
scriptPath = "/opt/ssl/check-and-setup-ssl.sh";
|
|
76
86
|
domainsFilePath = "/opt/ssl/domains.txt";
|
|
77
87
|
fs.writeFileSync(domainsFilePath, domainsTxt);
|
|
78
|
-
info("
|
|
88
|
+
info("已更新 /opt/ssl/domains.txt");
|
|
79
89
|
} else if (
|
|
80
90
|
scriptsDir &&
|
|
81
91
|
fs.existsSync(path.join(scriptsDir, "ssl", "check-and-setup-ssl.sh"))
|
|
@@ -85,9 +95,8 @@ export async function runSsl(opts: SslOptions): Promise<void> {
|
|
|
85
95
|
fs.writeFileSync(tmpDomains, domainsTxt);
|
|
86
96
|
domainsFilePath = tmpDomains;
|
|
87
97
|
} else {
|
|
88
|
-
error(
|
|
89
|
-
|
|
90
|
-
);
|
|
98
|
+
error("未找到 SSL 脚本");
|
|
99
|
+
info("请先执行 n2-deploy init 或设置 N2_DEPLOY_SCRIPTS_DIR");
|
|
91
100
|
process.exit(1);
|
|
92
101
|
}
|
|
93
102
|
|
|
@@ -124,7 +133,7 @@ export async function runSsl(opts: SslOptions): Promise<void> {
|
|
|
124
133
|
child.on("close", (code, signal) => {
|
|
125
134
|
if (code === 0) {
|
|
126
135
|
appendSslLog("run", "", "ok", "done");
|
|
127
|
-
|
|
136
|
+
success("SSL 证书配置完成");
|
|
128
137
|
resolve();
|
|
129
138
|
} else {
|
|
130
139
|
appendSslLog("run", "", "fail", `exit ${code} ${signal ?? ""}`);
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config file CRUD: load, get, set, delete, save for ~/.deploy/config.yaml.
|
|
3
|
+
* Keys: aliyun_access_key_id, aliyun_access_key_secret, api.backend_port,
|
|
4
|
+
* api.domains, admin.domains, tenant.domains, client.domains
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import type { DeployConfigResult } from "./domains.js";
|
|
10
|
+
import {
|
|
11
|
+
parseDeployConfig,
|
|
12
|
+
serializeDeployConfig,
|
|
13
|
+
} from "./domains.js";
|
|
14
|
+
|
|
15
|
+
export type ConfigKey =
|
|
16
|
+
| "aliyun_access_key_id"
|
|
17
|
+
| "aliyun_access_key_secret"
|
|
18
|
+
| "api.backend_port"
|
|
19
|
+
| "api.domains"
|
|
20
|
+
| "admin.domains"
|
|
21
|
+
| "tenant.domains"
|
|
22
|
+
| "client.domains";
|
|
23
|
+
|
|
24
|
+
const SECTION_KEYS: ConfigKey[] = [
|
|
25
|
+
"api.backend_port",
|
|
26
|
+
"api.domains",
|
|
27
|
+
"admin.domains",
|
|
28
|
+
"tenant.domains",
|
|
29
|
+
"client.domains",
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export function isValidConfigKey(key: string): key is ConfigKey {
|
|
33
|
+
return (
|
|
34
|
+
key === "aliyun_access_key_id" ||
|
|
35
|
+
key === "aliyun_access_key_secret" ||
|
|
36
|
+
SECTION_KEYS.includes(key as ConfigKey)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function loadConfig(configPath: string): DeployConfigResult {
|
|
41
|
+
const content = fs.readFileSync(configPath, "utf8");
|
|
42
|
+
return parseDeployConfig(content);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function saveConfig(
|
|
46
|
+
configPath: string,
|
|
47
|
+
data: DeployConfigResult
|
|
48
|
+
): void {
|
|
49
|
+
const dir = path.dirname(configPath);
|
|
50
|
+
if (dir && dir !== ".") {
|
|
51
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
fs.writeFileSync(configPath, serializeDeployConfig(data), "utf8");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getConfigValue(
|
|
57
|
+
data: DeployConfigResult,
|
|
58
|
+
key: ConfigKey
|
|
59
|
+
): string | number | string[] | undefined {
|
|
60
|
+
switch (key) {
|
|
61
|
+
case "aliyun_access_key_id":
|
|
62
|
+
return data.aliyunAccessKeyId;
|
|
63
|
+
case "aliyun_access_key_secret":
|
|
64
|
+
return data.aliyunAccessKeySecret;
|
|
65
|
+
case "api.backend_port":
|
|
66
|
+
return data.domainsConfig.api.backend_port;
|
|
67
|
+
case "api.domains":
|
|
68
|
+
return data.domainsConfig.api.domains;
|
|
69
|
+
case "admin.domains":
|
|
70
|
+
return data.domainsConfig.admin.domains;
|
|
71
|
+
case "tenant.domains":
|
|
72
|
+
return data.domainsConfig.tenant.domains;
|
|
73
|
+
case "client.domains":
|
|
74
|
+
return data.domainsConfig.client.domains;
|
|
75
|
+
default:
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function setConfigValue(
|
|
81
|
+
data: DeployConfigResult,
|
|
82
|
+
key: ConfigKey,
|
|
83
|
+
value: string | number | string[]
|
|
84
|
+
): void {
|
|
85
|
+
switch (key) {
|
|
86
|
+
case "aliyun_access_key_id":
|
|
87
|
+
data.aliyunAccessKeyId = String(value);
|
|
88
|
+
break;
|
|
89
|
+
case "aliyun_access_key_secret":
|
|
90
|
+
data.aliyunAccessKeySecret = String(value);
|
|
91
|
+
break;
|
|
92
|
+
case "api.backend_port":
|
|
93
|
+
data.domainsConfig.api.backend_port =
|
|
94
|
+
typeof value === "number" ? value : parseInt(String(value), 10);
|
|
95
|
+
break;
|
|
96
|
+
case "api.domains":
|
|
97
|
+
data.domainsConfig.api.domains = Array.isArray(value)
|
|
98
|
+
? value
|
|
99
|
+
: [String(value)];
|
|
100
|
+
break;
|
|
101
|
+
case "admin.domains":
|
|
102
|
+
data.domainsConfig.admin.domains = Array.isArray(value)
|
|
103
|
+
? value
|
|
104
|
+
: [String(value)];
|
|
105
|
+
break;
|
|
106
|
+
case "tenant.domains":
|
|
107
|
+
data.domainsConfig.tenant.domains = Array.isArray(value)
|
|
108
|
+
? value
|
|
109
|
+
: [String(value)];
|
|
110
|
+
break;
|
|
111
|
+
case "client.domains":
|
|
112
|
+
data.domainsConfig.client.domains = Array.isArray(value)
|
|
113
|
+
? value
|
|
114
|
+
: [String(value)];
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function deleteConfigValue(
|
|
120
|
+
data: DeployConfigResult,
|
|
121
|
+
key: ConfigKey
|
|
122
|
+
): void {
|
|
123
|
+
switch (key) {
|
|
124
|
+
case "aliyun_access_key_id":
|
|
125
|
+
data.aliyunAccessKeyId = undefined;
|
|
126
|
+
break;
|
|
127
|
+
case "aliyun_access_key_secret":
|
|
128
|
+
data.aliyunAccessKeySecret = undefined;
|
|
129
|
+
break;
|
|
130
|
+
case "api.backend_port":
|
|
131
|
+
data.domainsConfig.api.backend_port = 3000;
|
|
132
|
+
break;
|
|
133
|
+
case "api.domains":
|
|
134
|
+
data.domainsConfig.api.domains = [];
|
|
135
|
+
break;
|
|
136
|
+
case "admin.domains":
|
|
137
|
+
data.domainsConfig.admin.domains = [];
|
|
138
|
+
break;
|
|
139
|
+
case "tenant.domains":
|
|
140
|
+
data.domainsConfig.tenant.domains = [];
|
|
141
|
+
break;
|
|
142
|
+
case "client.domains":
|
|
143
|
+
data.domainsConfig.client.domains = [];
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|