ellm-proxy 0.0.3 → 0.0.7

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 (58) hide show
  1. package/README.md +60 -60
  2. package/bin/commands/autostart.js +37 -213
  3. package/bin/commands/config.js +8 -8
  4. package/bin/commands/credentials.js +100 -0
  5. package/bin/commands/pm2.js +28 -28
  6. package/bin/commands/service.js +107 -107
  7. package/bin/ellm-proxy.js +64 -61
  8. package/dist/server.js +141 -4
  9. package/dist/web-ui/assets/DashboardView-BCwrFrE2.js +1 -0
  10. package/dist/web-ui/assets/DashboardView-BHG4xIdN.css +1 -0
  11. package/dist/web-ui/assets/LayoutView-BUA8-IzK.css +1 -0
  12. package/dist/web-ui/assets/LayoutView-BePJfgia.js +1 -0
  13. package/dist/web-ui/assets/LoginView-Dp7NmXtf.js +1 -0
  14. package/dist/web-ui/assets/LoginView-DrhfslIY.css +1 -0
  15. package/dist/web-ui/assets/{LogsView-B2ctrpv4.js → LogsView-Dyy_zkgg.js} +2 -2
  16. package/dist/web-ui/assets/ProcessView-CpPpMtFg.js +1 -0
  17. package/dist/web-ui/assets/ProcessView-DK0TvDpR.css +1 -0
  18. package/dist/web-ui/assets/{ProvidersView-mb0KU16n.js → ProvidersView-CYFs76wJ.js} +2 -2
  19. package/dist/web-ui/assets/SettingsView-BkwyoOUb.css +1 -0
  20. package/dist/web-ui/assets/SettingsView-mPcaVAD2.js +1 -0
  21. package/dist/web-ui/assets/SetupView-DX6S_KpK.js +1 -0
  22. package/dist/web-ui/assets/SetupView-kPJXJ7dX.css +1 -0
  23. package/dist/web-ui/assets/config-CRWaUFQc.js +1 -0
  24. package/dist/web-ui/assets/{index-dPeMJjrz.js → index-X5PdkJlA.js} +28 -28
  25. package/dist/web-ui/assets/{providers-Dmz2p4Wd.js → providers-H_9x778D.js} +1 -1
  26. package/dist/web-ui/assets/useManagerApi-C1_COxKg.js +1 -0
  27. package/dist/web-ui/assets/{useToast-B3quFZyv.js → useToast-MtbkGJiM.js} +1 -1
  28. package/dist/web-ui/config.js +10 -10
  29. package/dist/web-ui/index.html +13 -13
  30. package/docs/DESIGN.md +99 -99
  31. package/ellm.config.example.json +2 -0
  32. package/manager/api/harness/backup.js +20 -20
  33. package/manager/api/harness/claude-code.js +54 -54
  34. package/manager/api/harness/codex.js +123 -123
  35. package/manager/api/harness/dsh.js +175 -175
  36. package/manager/api/harness/index.js +118 -118
  37. package/manager/api/service-settings.js +98 -0
  38. package/manager/api/service.js +3 -13
  39. package/manager/index.js +182 -170
  40. package/package.json +4 -1
  41. package/utils/autostart.js +219 -0
  42. package/utils/interpreter.js +14 -14
  43. package/utils/paths.js +45 -44
  44. package/utils/pm2.js +130 -130
  45. package/utils/service.js +205 -189
  46. package/utils/updater.js +233 -0
  47. package/dist/web-ui/assets/DashboardView-B71Dy3px.js +0 -1
  48. package/dist/web-ui/assets/DashboardView-qrQrkgLA.css +0 -1
  49. package/dist/web-ui/assets/LayoutView-C03GVB3X.js +0 -1
  50. package/dist/web-ui/assets/LoginView-Dg0mB6ok.css +0 -1
  51. package/dist/web-ui/assets/LoginView-XTF5dHvd.js +0 -1
  52. package/dist/web-ui/assets/ProcessView-DmbVABQV.js +0 -1
  53. package/dist/web-ui/assets/ProcessView-DzMqBJmd.css +0 -1
  54. package/dist/web-ui/assets/SettingsView-BDkymsrS.css +0 -1
  55. package/dist/web-ui/assets/SettingsView-BgLQVC2H.js +0 -1
  56. package/dist/web-ui/assets/config-BRaXq0LL.js +0 -1
  57. package/dist/web-ui/assets/useApi-C1Ngt_o5.js +0 -1
  58. package/dist/web-ui/assets/useManagerApi-BDWJz5o6.js +0 -1
package/README.md CHANGED
@@ -1,60 +1,60 @@
1
- # ellm-proxy
2
-
3
- 自研 LLM 网关(行为基准 litellm 1.100.0),pm2 后台守护一键启停(跨平台)。主服务(`ellm-proxy`)单进程提供网关与管理台 API;管理服务(`ellm-proxy-manager`,端口 = 主端口 + 1)托管管理界面 web-ui 并反代 `/api/*` 到主服务,主服务停止时仍可远程拉起。
4
-
5
- > 需求决策与架构设计详见 [docs/DESIGN.md](docs/DESIGN.md)。
6
-
7
- - `/v1/*` — LLM 网关端点(OpenAI / Anthropic 协议互转,多供应商路由、压缩、鉴权)
8
- - `/api/*` — 管理台 API(登录鉴权、配置热更新、用量/日志;主服务全局 CORS,供独立部署的 web-ui 跨源调用)
9
- - `http://127.0.0.1:<主端口+1>/` — 管理界面(manager 托管 web-ui,同源反代 `/api/*`)
10
-
11
- ## 安装
12
-
13
- 要求 Node.js **>= 22.13.0**(内置 `node:sqlite`,无需 flag)。
14
-
15
- ```bash
16
- npm i -g ellm-proxy
17
- ```
18
-
19
- ## 使用
20
-
21
- ```bash
22
- ellm-proxy -h # 查看帮助
23
- ellm-proxy start # pm2 后台守护启动(崩溃自动重启;首次自动创建配置)
24
- ellm-proxy stop # 停止服务
25
- ellm-proxy restart # 重启(先 stop 后 start,保证最新参数生效,选项同 start)
26
- ellm-proxy autostart on # Windows 开机自启(注册登录触发的计划任务)
27
- ellm-proxy autostart off # 关闭开机自启
28
- ellm-proxy config # 显示配置文件路径
29
- ```
30
-
31
- `start/stop/restart` 基于包内自带的 pm2(局部安装,进程名 `ellm-proxy`):服务由 pm2 守护常驻,关闭终端不影响运行,崩溃自动重启(封顶 10 次,防端口占用无限循环),日志写 `<appDir>/logs/`。服务要求的 Node ≥22.13 由 CLI 解析(当前 node 满足则直接用,否则经 get-node 自动下载一次并缓存),终端默认 node 版本无关。start/stop 成功后自动 `pm2 dump`,可随时用 `pm2 resurrect` 恢复。Windows、Linux、macOS 均可用;运行状态用 `pm2 ls` 查看。
32
-
33
- **开机自启(跨平台)**:`ellm-proxy autostart on` 注册登录触发的自启项(内容为 `pm2 resurrect`),三平台均无需管理员权限——Windows 写启动文件夹 `.cmd`、macOS 写 LaunchAgent(`launchctl load`)、Linux 写 systemd user unit(`systemctl --user enable`,WSL/无 systemd 环境会报错提示);`autostart off` 移除。
34
-
35
- ## 运行时布局(默认 `~/.ellm-proxy/`,均可被环境变量覆盖)
36
-
37
- | 内容 | 默认路径 | 环境变量覆盖 |
38
- | --- | --- | --- |
39
- | 配置文件 | `~/.ellm-proxy/ellm.config.json` | `ELLM_CONFIG`(或 `start --config <path>`) |
40
- | 数据目录(sqlite 日志/缓存) | `~/.ellm-proxy/data/` | `ELLM_DATA_DIR`(或 `start --data-dir <path>`) |
41
- | 应用目录(配置/数据基准) | `~/.ellm-proxy/` | `ELLM_APP_DIR`(CLI 按 `--working-dir` 自动注入) |
42
- | web-ui 静态资源 | 包内 `dist/web-ui/`(manager 托管) | `ELLM_WEBUI_DIR`(可选覆盖;manager 按自身位置自动推导) |
43
- | 运行日志 | `~/.ellm-proxy/logs/` | `start --working-dir <path>`(见 `ellm-proxy -h`) |
44
-
45
- 所有路径均支持相对路径(按当前工作目录展开)与绝对路径。服务端自身不加载任何 `.env` 文件;`ELLM_MASTER_KEY` 等环境变量直接在会话中导出即可。
46
-
47
- 首次执行 `ellm-proxy start` 会自动创建示例配置,之后两种方式修改:
48
-
49
- 1. 直接编辑配置文件;
50
- 2. 启动服务后访问管理界面 `http://127.0.0.1:<主端口+1>/` 在线修改(实时热更新)。
51
-
52
- 其他环境变量:`ELLM_PORT`(网关监听端口,或 `start --port`;管理服务固定 `ELLM_PORT + 1`,可用 `ELLM_MANAGER_PORT` 覆盖)、`HOST`(监听地址)、`ELLM_MASTER_KEY`(网关主密钥,低于配置文件 `masterKey` 字段)。启动时端口若被占用会自动顺延到下一个空闲端口(仅本次运行生效,不改配置文件),顺延结果会打印在启动摘要中。
53
-
54
- ## 本地开发(仓库内)
55
-
56
- 本包是 `ellm` monorepo 的发布载体:发布版 `dist/server.js` 由 `scripts/build-server.mjs` 从 `backend` 源码直接构建(esbuild 插件在构建期把路径模块替换为 `release/runtime-dir.ts` 包模式实现,backend 源码保持单模式零分支),`web-ui` 产物由 `pnpm build:web` 生成后拷入 `dist/web-ui/`(manager 托管):
57
-
58
- ```bash
59
- pnpm pack:proxy # 构建 web-ui → 构建发布版 + 聚合 → npm pack 生成 tgz
60
- ```
1
+ # ellm-proxy
2
+
3
+ 自研 LLM 网关(行为基准 litellm 1.100.0),pm2 后台守护一键启停(跨平台)。主服务(`ellm-proxy`)单进程提供网关与管理台 API;管理服务(`ellm-proxy-manager`,端口 = 主端口 + 1)托管管理界面 web-ui 并反代 `/api/*` 到主服务,主服务停止时仍可远程拉起。
4
+
5
+ > 需求决策与架构设计详见 [docs/DESIGN.md](docs/DESIGN.md)。
6
+
7
+ - `/v1/*` — LLM 网关端点(OpenAI / Anthropic 协议互转,多供应商路由、压缩、鉴权)
8
+ - `/api/*` — 管理台 API(登录鉴权、配置热更新、用量/日志;主服务全局 CORS,供独立部署的 web-ui 跨源调用)
9
+ - `http://127.0.0.1:<主端口+1>/` — 管理界面(manager 托管 web-ui,同源反代 `/api/*`)
10
+
11
+ ## 安装
12
+
13
+ 要求 Node.js **>= 22.13.0**(内置 `node:sqlite`,无需 flag)。
14
+
15
+ ```bash
16
+ npm i -g ellm-proxy
17
+ ```
18
+
19
+ ## 使用
20
+
21
+ ```bash
22
+ ellm-proxy -h # 查看帮助
23
+ ellm-proxy start # pm2 后台守护启动(崩溃自动重启;首次自动创建配置)
24
+ ellm-proxy stop # 停止服务
25
+ ellm-proxy restart # 重启(先 stop 后 start,保证最新参数生效,选项同 start)
26
+ ellm-proxy autostart on # Windows 开机自启(注册登录触发的计划任务)
27
+ ellm-proxy autostart off # 关闭开机自启
28
+ ellm-proxy config # 显示配置文件路径
29
+ ```
30
+
31
+ `start/stop/restart` 基于包内自带的 pm2(局部安装,进程名 `ellm-proxy`):服务由 pm2 守护常驻,关闭终端不影响运行,崩溃自动重启(封顶 10 次,防端口占用无限循环),日志写 `<appDir>/logs/`。服务要求的 Node ≥22.13 由 CLI 解析(当前 node 满足则直接用,否则经 get-node 自动下载一次并缓存),终端默认 node 版本无关。start/stop 成功后自动 `pm2 dump`,可随时用 `pm2 resurrect` 恢复。Windows、Linux、macOS 均可用;运行状态用 `pm2 ls` 查看。
32
+
33
+ **开机自启(跨平台)**:`ellm-proxy autostart on` 注册登录触发的自启项(内容为 `pm2 resurrect`),三平台均无需管理员权限——Windows 写启动文件夹 `.cmd`、macOS 写 LaunchAgent(`launchctl load`)、Linux 写 systemd user unit(`systemctl --user enable`,WSL/无 systemd 环境会报错提示);`autostart off` 移除。
34
+
35
+ ## 运行时布局(默认 `~/.ellm-proxy/`,均可被环境变量覆盖)
36
+
37
+ | 内容 | 默认路径 | 环境变量覆盖 |
38
+ | --- | --- | --- |
39
+ | 配置文件 | `~/.ellm-proxy/ellm.config.json` | `ELLM_CONFIG`(或 `start --config <path>`) |
40
+ | 数据目录(sqlite 日志/缓存) | `~/.ellm-proxy/data/` | `ELLM_DATA_DIR`(或 `start --data-dir <path>`) |
41
+ | 应用目录(配置/数据基准) | `~/.ellm-proxy/` | `ELLM_APP_DIR`(CLI 按 `--working-dir` 自动注入) |
42
+ | web-ui 静态资源 | 包内 `dist/web-ui/`(manager 托管) | `ELLM_WEBUI_DIR`(可选覆盖;manager 按自身位置自动推导) |
43
+ | 运行日志 | `~/.ellm-proxy/logs/` | `start --working-dir <path>`(见 `ellm-proxy -h`) |
44
+
45
+ 所有路径均支持相对路径(按当前工作目录展开)与绝对路径。服务端自身不加载任何 `.env` 文件;`ELLM_MASTER_KEY` 等环境变量直接在会话中导出即可。
46
+
47
+ 首次执行 `ellm-proxy start` 会自动创建示例配置,之后两种方式修改:
48
+
49
+ 1. 直接编辑配置文件;
50
+ 2. 启动服务后访问管理界面 `http://127.0.0.1:<主端口+1>/` 在线修改(实时热更新)。
51
+
52
+ 其他环境变量:`ELLM_PORT`(网关监听端口,或 `start --port`;管理服务固定 `ELLM_PORT + 1`,可用 `ELLM_MANAGER_PORT` 覆盖)、`HOST`(监听地址)、`ELLM_MASTER_KEY`(网关主密钥,低于配置文件 `masterKey` 字段)。启动时端口若被占用会自动顺延到下一个空闲端口(仅本次运行生效,不改配置文件),顺延结果会打印在启动摘要中。
53
+
54
+ ## 本地开发(仓库内)
55
+
56
+ 本包是 `ellm` monorepo 的发布载体:发布版 `dist/server.js` 由 `scripts/build-server.mjs` 从 `backend` 源码直接构建(esbuild 插件在构建期把路径模块替换为 `release/runtime-dir.ts` 包模式实现,backend 源码保持单模式零分支),`web-ui` 产物由 `pnpm build:web` 生成后拷入 `dist/web-ui/`(manager 托管):
57
+
58
+ ```bash
59
+ pnpm pack:proxy # 构建 web-ui → 构建发布版 + 聚合 → npm pack 生成 tgz
60
+ ```
@@ -1,213 +1,37 @@
1
- // autostart on|off 命令(跨平台开机自启,登录时触发,均无需管理员权限):
2
- // Windows: 启动文件夹 ellm-proxy-autostart.cmd(shell:startup——schtasks onlogon 需管理员,实测被拒)
3
- // macOS: LaunchAgent ~/Library/LaunchAgents/com.ellm-proxy.autostart.plist(launchctl load)
4
- // Linux: systemd user unit ~/.config/systemd/user/ellm-proxy.service(systemctl --user enable)
5
- // 三者内容一致:登录时执行 <nodePath> <包内 pm2 bin> resurrect(恢复最后一次 pm2 dump)。
6
- import { writeFileSync, rmSync, existsSync, mkdirSync } from "node:fs";
7
- import { homedir, platform } from "node:os";
8
- import { spawn } from "node:child_process";
9
- import { join } from "node:path";
10
- import { resolveInterpreter } from "../../utils/interpreter.js";
11
- import { pm2BinJs } from "../../utils/pm2.js";
12
- import { defaultAppDir } from "../../utils/paths.js";
13
-
14
- const AUTOSTART_CMD = "ellm-proxy-autostart.cmd"; // Windows
15
- const PLIST_LABEL = "com.ellm-proxy.autostart"; // macOS
16
- const LINUX_UNIT = "ellm-proxy.service"; // Linux
17
-
18
- /** 用户启动文件夹(Windows shell:startup):登录时自动执行其中的脚本 */
19
- function windowsStartupDir() {
20
- const dir = process.env["APPDATA"];
21
- if (!dir) throw new Error("无法定位启动文件夹(APPDATA 环境变量未设置)。");
22
- return join(dir, "Microsoft", "Windows", "Start Menu", "Programs", "Startup");
23
- }
24
-
25
- /** spawn 执行外部命令,返回退出码;进程无法启动(ENOENT 等)返回 127 */
26
- function runExit(cmd, args) {
27
- return new Promise((resolvePromise) => {
28
- const child = spawn(cmd, args, { stdio: "ignore", shell: false });
29
- child.on("error", () => resolvePromise(127));
30
- child.on("exit", (code) => resolvePromise(code ?? 1));
31
- });
32
- }
33
-
34
- // —— Windows:启动文件夹 .cmd ————————————————————————————————————————
35
-
36
- function windowsPaths() {
37
- return { itemPath: join(windowsStartupDir(), AUTOSTART_CMD) };
38
- }
39
-
40
- async function windowsOn(nodePath, pm2Js) {
41
- // cmd 内容全 ASCII(含中文的注释交给外部帮助输出,避免 cmd 在 GBK 代码页下乱码)
42
- const body = [
43
- "@echo off",
44
- `rem Auto-generated by "ellm-proxy autostart on". Disable with "ellm-proxy autostart off".`,
45
- `"${nodePath}" "${pm2Js}" resurrect >nul 2>&1`,
46
- "",
47
- ].join("\r\n");
48
- const { itemPath } = windowsPaths();
49
- writeFileSync(itemPath, body, "utf-8");
50
- }
51
-
52
- async function windowsOff() {
53
- const { itemPath } = windowsPaths();
54
- if (!existsSync(itemPath)) return false;
55
- rmSync(itemPath, { force: true });
56
- return true;
57
- }
58
-
59
- // —— macOS:LaunchAgent plist ——————————————————————————————————————————
60
-
61
- function darwinPaths() {
62
- return { itemPath: join(homedir(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`) };
63
- }
64
-
65
- async function darwinOn(nodePath, pm2Js) {
66
- const { itemPath } = darwinPaths();
67
- const logPath = join(defaultAppDir, "logs", "autostart.log");
68
- mkdirSync(join(defaultAppDir, "logs"), { recursive: true });
69
- const body = [
70
- '<?xml version="1.0" encoding="UTF-8"?>',
71
- '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
72
- '<plist version="1.0">',
73
- "<dict>",
74
- ` <key>Label</key><string>${PLIST_LABEL}</string>`,
75
- " <key>ProgramArguments</key>",
76
- " <array>",
77
- ` <string>${nodePath}</string>`,
78
- ` <string>${pm2Js}</string>`,
79
- " <string>resurrect</string>",
80
- " </array>",
81
- " <key>RunAtLoad</key><true/>",
82
- ` <key>StandardOutPath</key><string>${logPath}</string>`,
83
- ` <key>StandardErrorPath</key><string>${logPath}</string>`,
84
- "</dict>",
85
- "</plist>",
86
- "",
87
- ].join("\n");
88
- writeFileSync(itemPath, body, "utf-8");
89
- // 先卸载旧定义再加载(幂等;未加载过时 unload 报错属正常)
90
- await runExit("launchctl", ["unload", itemPath]);
91
- const code = await runExit("launchctl", ["load", itemPath]);
92
- if (code !== 0) {
93
- throw new Error(`launchctl load 失败(退出码 ${code})。请手动执行: launchctl load "${itemPath}"`);
94
- }
95
- }
96
-
97
- async function darwinOff() {
98
- const { itemPath } = darwinPaths();
99
- if (!existsSync(itemPath)) return false;
100
- await runExit("launchctl", ["unload", itemPath]);
101
- rmSync(itemPath, { force: true });
102
- return true;
103
- }
104
-
105
- // —— Linux:systemd user unit ——————————————————————————————————————————
106
-
107
- function linuxPaths() {
108
- return { itemPath: join(homedir(), ".config", "systemd", "user", LINUX_UNIT) };
109
- }
110
-
111
- async function linuxOn(nodePath, pm2Js) {
112
- const { itemPath } = linuxPaths();
113
- mkdirSync(join(homedir(), ".config", "systemd", "user"), { recursive: true });
114
- const body = [
115
- "[Unit]",
116
- "Description=ellm-proxy autostart (pm2 resurrect)",
117
- "After=default.target",
118
- "",
119
- "[Service]",
120
- "Type=oneshot",
121
- `ExecStart="${nodePath}" "${pm2Js}" resurrect`,
122
- "",
123
- "[Install]",
124
- "WantedBy=default.target",
125
- "",
126
- ].join("\n");
127
- writeFileSync(itemPath, body, "utf-8");
128
- const reload = await runExit("systemctl", ["--user", "daemon-reload"]);
129
- if (reload !== 0) {
130
- throw new Error(
131
- `systemctl --user daemon-reload 失败(退出码 ${reload})。当前环境可能无 systemd(WSL/容器),请手动启用或删除: ${itemPath}`,
132
- );
133
- }
134
- const enable = await runExit("systemctl", ["--user", "enable", LINUX_UNIT]);
135
- if (enable !== 0) {
136
- throw new Error(
137
- `systemctl --user enable 失败(退出码 ${enable})。请手动执行: systemctl --user enable ${LINUX_UNIT}`,
138
- );
139
- }
140
- }
141
-
142
- async function linuxOff() {
143
- const { itemPath } = linuxPaths();
144
- if (!existsSync(itemPath)) return false;
145
- await runExit("systemctl", ["--user", "disable", LINUX_UNIT]); // 容错:未启用过时报错不影响删除
146
- rmSync(itemPath, { force: true });
147
- await runExit("systemctl", ["--user", "daemon-reload"]);
148
- return true;
149
- }
150
-
151
- // —— 命令入口:平台分支 ————————————————————————————————————————————————
152
-
153
- async function autostartOn() {
154
- const nodePath = await resolveInterpreter();
155
- const pm2Js = pm2BinJs();
156
- let itemPath;
157
- switch (platform()) {
158
- case "win32":
159
- itemPath = windowsPaths().itemPath;
160
- await windowsOn(nodePath, pm2Js);
161
- break;
162
- case "darwin":
163
- itemPath = darwinPaths().itemPath;
164
- await darwinOn(nodePath, pm2Js);
165
- break;
166
- case "linux":
167
- itemPath = linuxPaths().itemPath;
168
- await linuxOn(nodePath, pm2Js);
169
- break;
170
- default:
171
- throw new Error(`暂不支持的平台: ${platform()}。可改用 pm2 自带的 pm2 startup。`);
172
- }
173
- console.log("开机自启已开启(登录时触发):");
174
- console.log(` 自启项 : ${itemPath}(pm2 resurrect 恢复服务)`);
175
- console.log(" 禁用方式 : ellm-proxy autostart off");
176
- }
177
-
178
- async function autostartOff() {
179
- let removed;
180
- switch (platform()) {
181
- case "win32":
182
- removed = await windowsOff();
183
- break;
184
- case "darwin":
185
- removed = await darwinOff();
186
- break;
187
- case "linux":
188
- removed = await linuxOff();
189
- break;
190
- default:
191
- throw new Error(`暂不支持的平台: ${platform()}。可改用 pm2 自带的 pm2 startup。`);
192
- }
193
- if (!removed) {
194
- console.log("未找到自启项(可能未开启开机自启),无需操作。");
195
- return;
196
- }
197
- console.log("开机自启已关闭(自启项已移除)。");
198
- }
199
-
200
- export function register(cli, run) {
201
- cli
202
- .command(
203
- "autostart <action>",
204
- "开机自启(登录时触发):on=注册自启项(Win 启动文件夹/mac LaunchAgent/Linux systemd user),off=移除",
205
- )
206
- .action((action) => {
207
- if (action !== "on" && action !== "off") {
208
- console.error(`未知参数: ${action}(用法: ellm-proxy autostart on|off)`);
209
- process.exit(1);
210
- }
211
- return run(action === "on" ? autostartOn : autostartOff);
212
- });
213
- }
1
+ // autostart on|off 命令(CLI 薄壳):平台实现与状态探测在 utils/autostart.js(被管理服务复用),
2
+ // 本文件只做命令装配与人读输出。
3
+ import { autostartStatus, enableAutostart, disableAutostart } from "../../utils/autostart.js";
4
+
5
+ async function autostartOn() {
6
+ const { itemPath } = await enableAutostart();
7
+ console.log("开机自启已开启(登录时触发):");
8
+ console.log(` 自启项 : ${itemPath}(pm2 resurrect 恢复服务)`);
9
+ console.log(" 禁用方式 : ellm-proxy autostart off");
10
+ }
11
+
12
+ async function autostartOff() {
13
+ const { removed } = await disableAutostart();
14
+ if (!removed) {
15
+ console.log("未找到自启项(可能未开启开机自启),无需操作。");
16
+ return;
17
+ }
18
+ console.log("开机自启已关闭(自启项已移除)。");
19
+ }
20
+
21
+ export function register(cli, run) {
22
+ cli
23
+ .command(
24
+ "autostart <action>",
25
+ "开机自启(登录时触发):on=注册自启项(Win 启动文件夹/mac LaunchAgent/Linux systemd user),off=移除",
26
+ )
27
+ .action((action) => {
28
+ if (action !== "on" && action !== "off") {
29
+ console.error(`未知参数: ${action}(用法: ellm-proxy autostart on|off)`);
30
+ process.exit(1);
31
+ }
32
+ return run(action === "on" ? autostartOn : autostartOff);
33
+ });
34
+ }
35
+
36
+ // 状态探测保留导出(潜在 CLI status 输出用;当前 manager 走 utils 直连)
37
+ export { autostartStatus };
@@ -1,8 +1,8 @@
1
- // config 命令:显示配置文件路径(不存在时自动创建示例配置)。
2
- import { ensureConfig } from "../../utils/paths.js";
3
-
4
- export function register(cli) {
5
- cli.command("config", "显示配置文件路径(不存在时自动创建示例配置)").action((opts) => {
6
- console.log(ensureConfig(opts.config));
7
- });
8
- }
1
+ // config 命令:显示配置文件路径(不存在时自动创建示例配置)。
2
+ import { ensureConfig } from "../../utils/paths.js";
3
+
4
+ export function register(cli) {
5
+ cli.command("config", "显示配置文件路径(不存在时自动创建示例配置)").action((opts) => {
6
+ console.log(ensureConfig(opts.config));
7
+ });
8
+ }
@@ -0,0 +1,100 @@
1
+ // 凭据维护 CLI:主密钥与管理台登录账号的查看/设置/清除/重置(忘记凭据时的自救通道)。
2
+ // ellm-proxy master-key 查看当前主密钥
3
+ // ellm-proxy master-key <key> 设置自定义主密钥(≥8 位)
4
+ // ellm-proxy master-key --random 随机生成主密钥
5
+ // ellm-proxy master-key --clear 清除主密钥(/v1 恢复免鉴权,仅建议本机使用)
6
+ // ellm-proxy account 查看当前登录账号
7
+ // ellm-proxy account <username> <password> 设置登录账号(密码 ≥6 位)
8
+ // ellm-proxy account --reset 重置为默认账号 admin/admin
9
+ // 直接改写 ellm.config.json(与后端同一查找链);后端每请求动态读取该文件,改完即时生效无需重启。
10
+ import { randomBytes } from "node:crypto";
11
+ import { readFileSync, writeFileSync } from "node:fs";
12
+ import { configPath, ensureConfig } from "../../utils/paths.js";
13
+
14
+ const MIN_MASTER_KEY = 8;
15
+ const MIN_PASSWORD = 6;
16
+
17
+ function loadConfig(override) {
18
+ const path = configPath(override);
19
+ return { path, config: JSON.parse(readFileSync(path, "utf-8")) };
20
+ }
21
+
22
+ function saveConfig(path, config) {
23
+ writeFileSync(path, JSON.stringify(config, null, 2) + "\n", "utf-8");
24
+ }
25
+
26
+ export function register(cli, run) {
27
+ cli
28
+ .command("master-key [key]", "查看/设置主密钥(--random 随机生成 · --clear 清除;设置后 /v1/* 需携带 Bearer <key>)")
29
+ .option("--random", "随机生成 43 位强密钥")
30
+ .option("--clear", "清除主密钥(/v1/* 恢复免鉴权,仅建议本机使用)")
31
+ .option("--config <path>", "配置文件路径(默认 ~/.ellm-proxy/ellm.config.json)")
32
+ .action((key, opts) =>
33
+ run(() => {
34
+ ensureConfig(opts.config);
35
+ const { path, config } = loadConfig(opts.config);
36
+ if (opts.random) {
37
+ const generated = randomBytes(32).toString("base64url");
38
+ saveConfig(path, { ...config, masterKey: generated });
39
+ console.log(`已生成随机主密钥:\n${generated}\n(请妥善保存,调用 /v1/* 时作为 Bearer token 使用)`);
40
+ } else if (opts.clear) {
41
+ saveConfig(path, { ...config, masterKey: "" });
42
+ console.log("已清除主密钥:/v1/* 恢复免鉴权(本地个人使用模式)。");
43
+ } else if (key) {
44
+ const trimmed = String(key).trim();
45
+ if (trimmed.length < MIN_MASTER_KEY) throw new Error(`主密钥至少 ${MIN_MASTER_KEY} 位`);
46
+ saveConfig(path, { ...config, masterKey: trimmed });
47
+ console.log("主密钥已更新,调用 /v1/* 时使用 Bearer <新密钥>。");
48
+ } else {
49
+ const current = typeof config.masterKey === "string" ? config.masterKey.trim() : "";
50
+ if (current === "") {
51
+ console.log("当前未设置主密钥:/v1/* 免鉴权(本地个人使用模式)。");
52
+ console.log(`设置方式:ellm-proxy master-key <key>(≥${MIN_MASTER_KEY} 位)或 --random`);
53
+ } else {
54
+ console.log(`当前主密钥: ${current}`);
55
+ }
56
+ if (process.env["ELLM_MASTER_KEY"]) {
57
+ console.log("注意:检测到环境变量 ELLM_MASTER_KEY(配置文件未设置时才生效)。");
58
+ }
59
+ }
60
+ console.log(`配置文件: ${path}(后端动态读取,无需重启即时生效)`);
61
+ }),
62
+ );
63
+
64
+ cli
65
+ .command("account [username] [password]", "查看/设置管理台登录账号(--reset 重置为 admin/admin;改后旧登录态立即失效)")
66
+ .option("--reset", "重置为默认账号 admin/admin")
67
+ .option("--config <path>", "配置文件路径(默认 ~/.ellm-proxy/ellm.config.json)")
68
+ .action((username, password, opts) =>
69
+ run(() => {
70
+ ensureConfig(opts.config);
71
+ const { path, config } = loadConfig(opts.config);
72
+ const webUi = typeof config["web-ui"] === "object" && config["web-ui"] !== null ? config["web-ui"] : {};
73
+ if (opts.reset) {
74
+ saveConfig(path, { ...config, "web-ui": { ...webUi, "login-account": {} } });
75
+ console.log("已重置为默认账号 admin/admin(旧登录 token 立即失效)。");
76
+ } else if (username && password) {
77
+ const name = String(username).trim();
78
+ const pass = String(password);
79
+ if (name === "") throw new Error("用户名不能为空");
80
+ if (pass.length < MIN_PASSWORD) throw new Error(`密码至少 ${MIN_PASSWORD} 位`);
81
+ saveConfig(path, {
82
+ ...config,
83
+ "web-ui": { ...webUi, "login-account": { username: name, password: pass } },
84
+ });
85
+ console.log(`登录账号已更新为 ${name}(旧登录 token 立即失效)。`);
86
+ } else {
87
+ const account = webUi["login-account"] ?? {};
88
+ const name = typeof account.username === "string" && account.username.trim() !== "" ? account.username.trim() : "";
89
+ if (name === "") {
90
+ console.log("当前未在配置文件中设置账号:使用 admin/admin(或 WEBUI_USERNAME/WEBUI_PASSWORD 环境变量)。");
91
+ } else {
92
+ console.log(`当前账号: ${name}`);
93
+ console.log(`当前密码: ${account.password}`);
94
+ }
95
+ console.log("设置方式:ellm-proxy account <username> <password>,重置:ellm-proxy account --reset");
96
+ }
97
+ console.log(`配置文件: ${path}(后端动态读取,无需重启即时生效)`);
98
+ }),
99
+ );
100
+ }
@@ -1,28 +1,28 @@
1
- // pm2 透传命令:ellm-proxy pm2 <任意 pm2 参数>(如 list/ls/logs/monit/describe/flush)。
2
- // 用包内局部 pm2 执行,用户无需自装全局 pm2;stdio 直接继承,logs/monit 等交互命令可实时输出、
3
- // Ctrl-C 退出。透传参数不经 cac 解析(在 CLI 入口先于 cac 拦截),pm2 的 -- 选项原样可达。
4
- import { spawn } from "node:child_process";
5
- import { pm2BinJs } from "../../utils/pm2.js";
6
-
7
- /** 以包内 pm2 CLI 执行任意参数(node 版本无关紧要:pm2 bin 脚本兼容;daemon 连接与 interpreter 无关) */
8
- export async function doPassthrough(args) {
9
- if (args.length === 0) {
10
- args = ["list"]; // 无参数 = 默认展示进程列表
11
- }
12
- return new Promise((resolve, reject) => {
13
- const child = spawn(process.execPath, [pm2BinJs(), ...args], { stdio: "inherit", shell: false });
14
- child.on("error", reject);
15
- // pm2 退出码原样透传;exit 回调内 process.exit 结束进程(promise 永不 resolve)
16
- child.on("exit", (code) => process.exit(code ?? 1));
17
- });
18
- }
19
-
20
- export function register(cli, run) {
21
- // cac 仅声明命令用于 -h 展示;实际执行走 CLI 入口的 pm2 拦截(保证 pm2 选项不被 cac 解析吞掉)
22
- cli
23
- .command(
24
- "pm2 [args...]",
25
- "透传执行包内 pm2 任意子命令(如 list/ls/logs/monit/describe/flush;pm2 的 -- 选项原样透传)",
26
- )
27
- .action(() => {});
28
- }
1
+ // pm2 透传命令:ellm-proxy pm2 <任意 pm2 参数>(如 list/ls/logs/monit/describe/flush)。
2
+ // 用包内局部 pm2 执行,用户无需自装全局 pm2;stdio 直接继承,logs/monit 等交互命令可实时输出、
3
+ // Ctrl-C 退出。透传参数不经 cac 解析(在 CLI 入口先于 cac 拦截),pm2 的 -- 选项原样可达。
4
+ import { spawn } from "node:child_process";
5
+ import { pm2BinJs } from "../../utils/pm2.js";
6
+
7
+ /** 以包内 pm2 CLI 执行任意参数(node 版本无关紧要:pm2 bin 脚本兼容;daemon 连接与 interpreter 无关) */
8
+ export async function doPassthrough(args) {
9
+ if (args.length === 0) {
10
+ args = ["list"]; // 无参数 = 默认展示进程列表
11
+ }
12
+ return new Promise((resolve, reject) => {
13
+ const child = spawn(process.execPath, [pm2BinJs(), ...args], { stdio: "inherit", shell: false });
14
+ child.on("error", reject);
15
+ // pm2 退出码原样透传;exit 回调内 process.exit 结束进程(promise 永不 resolve)
16
+ child.on("exit", (code) => process.exit(code ?? 1));
17
+ });
18
+ }
19
+
20
+ export function register(cli, run) {
21
+ // cac 仅声明命令用于 -h 展示;实际执行走 CLI 入口的 pm2 拦截(保证 pm2 选项不被 cac 解析吞掉)
22
+ cli
23
+ .command(
24
+ "pm2 [args...]",
25
+ "透传执行包内 pm2 任意子命令(如 list/ls/logs/monit/describe/flush;pm2 的 -- 选项原样透传)",
26
+ )
27
+ .action(() => {});
28
+ }