@surenkid/acli-hub 1.0.0 → 1.0.2
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/.env.example +11 -6
- package/README.md +31 -24
- package/package.json +1 -1
- package/server/index.js +16 -8
- package/server/routes/files.js +2 -2
- package/server/services/session-manager.js +3 -2
package/.env.example
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
# Main server port (backend + built frontend in production)
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
ACLI_HUB_PORT=3000
|
|
3
|
+
# Bind address — default 127.0.0.1 (local only). Use 0.0.0.0 for LAN/remote access,
|
|
4
|
+
# and ALWAYS set ACLI_HUB_AUTH_PASSWORD when binding beyond loopback.
|
|
5
|
+
#ACLI_HUB_HOST=0.0.0.0
|
|
4
6
|
|
|
5
7
|
# Internal ttyd port range (localhost only, proxied through the main port)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
ACLI_HUB_TTYD_PORT_RANGE_START=7681
|
|
9
|
+
ACLI_HUB_TTYD_PORT_RANGE_END=7780
|
|
8
10
|
|
|
9
11
|
# Maximum size per uploaded file, in MiB. 0 = unlimited.
|
|
10
|
-
|
|
12
|
+
ACLI_HUB_UPLOAD_MAX_MB=500
|
|
11
13
|
|
|
12
14
|
# Login password for the web UI. Leave UNSET to disable authentication
|
|
13
15
|
# (local development). Generate a strong one with: openssl rand -base64 24
|
|
14
16
|
# When set, all pages/API/terminal/WebSocket access requires a 7-day
|
|
15
17
|
# login session (cookie).
|
|
16
|
-
|
|
18
|
+
ACLI_HUB_AUTH_PASSWORD=
|
|
19
|
+
|
|
20
|
+
# Config locations (cwd is never read): ~/.config/acli-hub/.env for the installed CLI
|
|
21
|
+
# ($XDG_CONFIG_HOME respected); the repo root .env when running from source.
|
package/README.md
CHANGED
|
@@ -56,39 +56,46 @@ brew install ttyd tmux
|
|
|
56
56
|
## 🚀 快速开始
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
cd frontend && npm install && cd ..
|
|
62
|
-
|
|
63
|
-
# 构建前端
|
|
64
|
-
npm run build
|
|
65
|
-
|
|
66
|
-
# 启动服务
|
|
67
|
-
npm start
|
|
59
|
+
npm install -g @surenkid/acli-hub
|
|
60
|
+
acli-hub
|
|
68
61
|
```
|
|
69
62
|
|
|
70
|
-
浏览器打开 `http://localhost:3000`
|
|
63
|
+
浏览器打开 `http://localhost:3000` 即可使用。登录密码等配置放在 `~/.config/acli-hub/.env`(模板见下方配置项)。
|
|
64
|
+
|
|
65
|
+
> 也可以免安装直接体验:`npx @surenkid/acli-hub`
|
|
71
66
|
|
|
72
|
-
## 👨💻
|
|
67
|
+
## 👨💻 从源码运行
|
|
73
68
|
|
|
74
69
|
```bash
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
git clone https://cnb.cool/surenkid/acli-hub.git
|
|
71
|
+
cd acli-hub
|
|
72
|
+
npm install
|
|
73
|
+
cd frontend && npm install && cd ..
|
|
77
74
|
|
|
78
|
-
|
|
75
|
+
npm run dev # 开发模式:后端 3000 + Vite 5173(热更新)
|
|
76
|
+
# 或构建生产版运行
|
|
77
|
+
npm run build && npm start
|
|
78
|
+
```
|
|
79
79
|
|
|
80
80
|
## ⚙️ 配置项
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
配置从固定位置读取,**与启动目录无关**(避免与所在项目的 `.env` 冲突):
|
|
83
|
+
|
|
84
|
+
1. `~/.config/acli-hub/.env` — npm 安装(`npx`/全局)使用的配置,尊重 `$XDG_CONFIG_HOME`(模板参考包内 `.env.example`)
|
|
85
|
+
2. 源码运行(`npm run dev`)时为仓库根目录的 `.env`
|
|
86
|
+
|
|
87
|
+
进程环境变量优先级最高。注意:启动目录同时决定新建终端会话的起始目录,建议固定一个目录启动。
|
|
88
|
+
|
|
89
|
+
配置项:
|
|
83
90
|
|
|
84
|
-
| 变量
|
|
85
|
-
|
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
91
|
+
| 变量 | 说明 | 默认值 |
|
|
92
|
+
| ------------------------------- | ------------------------------------------ | --------- |
|
|
93
|
+
| `ACLI_HUB_PORT` | 服务监听端口 | `3000` |
|
|
94
|
+
| `ACLI_HUB_HOST` | 监听地址;默认仅本机,局域网访问设 `0.0.0.0`(务必同时设密码) | `127.0.0.1` |
|
|
95
|
+
| `ACLI_HUB_AUTH_PASSWORD` | 登录密码;不设置则关闭认证(仅限本地开发) | _(不设置)_ |
|
|
96
|
+
| `ACLI_HUB_TTYD_PORT_RANGE_START` | ttyd 内部端口范围起始 | `7681` |
|
|
97
|
+
| `ACLI_HUB_TTYD_PORT_RANGE_END` | ttyd 内部端口范围结束 | `7780` |
|
|
98
|
+
| `ACLI_HUB_UPLOAD_MAX_MB` | 单文件上传上限(MiB),0 为不限 | `500` |
|
|
92
99
|
|
|
93
100
|
### 📁 文件面板说明
|
|
94
101
|
|
|
@@ -98,7 +105,7 @@ npm run dev
|
|
|
98
105
|
|
|
99
106
|
### 🔐 公网暴露
|
|
100
107
|
|
|
101
|
-
设置 `
|
|
108
|
+
设置 `ACLI_HUB_AUTH_PASSWORD` 后,建议将 ACLI Hub 部署在 HTTPS 反向代理(如 Caddy / Nginx / frp)之后,只公开 443 端口。登录会话为 7 天有效的 HttpOnly Cookie,终端 iframe 与 WebSocket 均受同一凭据保护。
|
|
102
109
|
|
|
103
110
|
## 📄 License
|
|
104
111
|
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
require('
|
|
3
|
-
const express = require('express');
|
|
2
|
+
const os = require('os');
|
|
4
3
|
const path = require('path');
|
|
4
|
+
// Config is read from fixed locations, never the working directory (which
|
|
5
|
+
// would collide with whatever project the user happens to be in):
|
|
6
|
+
// 1. <package root>/.env — the source checkout during development
|
|
7
|
+
// 2. ~/.config/acli-hub/.env ($XDG_CONFIG_HOME) — the installed CLI's config
|
|
8
|
+
// dotenv never overrides variables that are already set, so real env vars
|
|
9
|
+
// always win.
|
|
10
|
+
require('dotenv').config({ path: path.join(__dirname, '..', '.env') });
|
|
11
|
+
require('dotenv').config({ path: path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'acli-hub', '.env') });
|
|
12
|
+
const express = require('express');
|
|
5
13
|
const http = require('http');
|
|
6
14
|
const { createProxyMiddleware } = require('http-proxy-middleware');
|
|
7
15
|
const SessionManager = require('./services/session-manager');
|
|
@@ -11,10 +19,10 @@ const sessionsRoute = require('./routes/sessions');
|
|
|
11
19
|
const filesRoute = require('./routes/files');
|
|
12
20
|
const setupWebSocket = require('./ws');
|
|
13
21
|
|
|
14
|
-
const PORT = parseInt(process.env.
|
|
15
|
-
const HOST = process.env.
|
|
16
|
-
const TTYD_PORT_START = parseInt(process.env.
|
|
17
|
-
const TTYD_PORT_END = parseInt(process.env.
|
|
22
|
+
const PORT = parseInt(process.env.ACLI_HUB_PORT || '3000', 10);
|
|
23
|
+
const HOST = process.env.ACLI_HUB_HOST || '127.0.0.1';
|
|
24
|
+
const TTYD_PORT_START = parseInt(process.env.ACLI_HUB_TTYD_PORT_RANGE_START || '7681', 10);
|
|
25
|
+
const TTYD_PORT_END = parseInt(process.env.ACLI_HUB_TTYD_PORT_RANGE_END || '7780', 10);
|
|
18
26
|
|
|
19
27
|
const app = express();
|
|
20
28
|
const server = http.createServer(app);
|
|
@@ -26,7 +34,7 @@ app.use((req, res, next) => {
|
|
|
26
34
|
});
|
|
27
35
|
|
|
28
36
|
const sessionManager = new SessionManager(TTYD_PORT_START, TTYD_PORT_END);
|
|
29
|
-
const auth = new AuthService(process.env.
|
|
37
|
+
const auth = new AuthService(process.env.ACLI_HUB_AUTH_PASSWORD);
|
|
30
38
|
|
|
31
39
|
function requireAuth(req, res, next) {
|
|
32
40
|
if (!auth.isEnabled()) return next();
|
|
@@ -122,7 +130,7 @@ server.listen(PORT, HOST, async () => {
|
|
|
122
130
|
if (auth.isEnabled()) {
|
|
123
131
|
console.log('Auth enabled: login required (sessions last 7 days)');
|
|
124
132
|
} else {
|
|
125
|
-
console.warn('WARNING:
|
|
133
|
+
console.warn('WARNING: ACLI_HUB_AUTH_PASSWORD is not set — running WITHOUT authentication. Do not expose this to the internet.');
|
|
126
134
|
}
|
|
127
135
|
const restored = await sessionManager.restore();
|
|
128
136
|
if (restored.length > 0) {
|
package/server/routes/files.js
CHANGED
|
@@ -69,10 +69,10 @@ function respondError(res, err) {
|
|
|
69
69
|
res.status(status).json({ error: err.message });
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
module.exports = function filesRoute({ platform = process.platform, home = os.homedir(), uploadMaxMb = process.env.
|
|
72
|
+
module.exports = function filesRoute({ platform = process.platform, home = os.homedir(), uploadMaxMb = process.env.ACLI_HUB_UPLOAD_MAX_MB ?? '500' } = {}) {
|
|
73
73
|
const router = express.Router();
|
|
74
74
|
const maximum = Number(uploadMaxMb);
|
|
75
|
-
if (!Number.isFinite(maximum) || maximum < 0) throw new Error('
|
|
75
|
+
if (!Number.isFinite(maximum) || maximum < 0) throw new Error('ACLI_HUB_UPLOAD_MAX_MB must be a non-negative number');
|
|
76
76
|
const uploadLimit = maximum * 1024 * 1024;
|
|
77
77
|
const trash = platform === 'darwin' ? 'mv-trash' : 'rm';
|
|
78
78
|
// Serialize app mutations so concurrent renames/trash moves cannot overwrite
|
|
@@ -305,7 +305,8 @@ class SessionManager extends EventEmitter {
|
|
|
305
305
|
async restore() {
|
|
306
306
|
let output;
|
|
307
307
|
try {
|
|
308
|
-
|
|
308
|
+
// stderr silenced: tmux prints "no server running" whenever the first session does not exist yet
|
|
309
|
+
output = execSync(`tmux list-sessions -F '#{session_name}\t#{@hub-managed}\t#{@hub-alias}\t#{@hub-root}'`, { stdio: ['ignore', 'pipe', 'ignore'], env: SPAWN_ENV }).toString();
|
|
309
310
|
} catch (_) {
|
|
310
311
|
return []; // no tmux server running or no sessions at all
|
|
311
312
|
}
|
|
@@ -373,7 +374,7 @@ class SessionManager extends EventEmitter {
|
|
|
373
374
|
try {
|
|
374
375
|
output = execSync(
|
|
375
376
|
`tmux list-panes -a -F '#{session_name}\t#{window_active}\t#{pane_active}\t#{pane_current_command}\t#{pane_title}'`,
|
|
376
|
-
{ env: SPAWN_ENV }
|
|
377
|
+
{ stdio: ['ignore', 'pipe', 'ignore'], env: SPAWN_ENV }
|
|
377
378
|
).toString();
|
|
378
379
|
} catch (_) {
|
|
379
380
|
return; // no tmux server running
|