v1.1.0: 支持 Windows 服务端部署
- 新增跨平台进程终止 killProcess(),Windows 使用 taskkill,Linux 使用 SIGTERM/SIGKILL - spawn 增加 windowsHide 参数,HOME 路径增加 USERPROFILE fallback - 新增 start.bat 一键启动脚本 - README 快速开始分 Linux/Windows 两部分,新增 Windows 部署章节 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
48
README.md
48
README.md
@@ -38,21 +38,28 @@ https://github.com/ZgDaniel/cc-web 给我装!
|
|||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
|
### Linux / macOS
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 克隆项目
|
git clone https://github.com/ZgDaniel/cc-web.git
|
||||||
git clone https://github.com/your-username/cc-web.git
|
|
||||||
cd cc-web
|
cd cc-web
|
||||||
|
|
||||||
# 安装依赖
|
|
||||||
npm install
|
npm install
|
||||||
|
cp .env.example .env # 可选,不设密码则首次启动自动生成
|
||||||
# 创建配置文件(可选,不设密码则首次启动自动生成)
|
|
||||||
cp .env.example .env
|
|
||||||
|
|
||||||
# 启动
|
|
||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
git clone https://github.com/ZgDaniel/cc-web.git
|
||||||
|
cd cc-web
|
||||||
|
npm install
|
||||||
|
copy .env.example .env & REM 可选
|
||||||
|
```
|
||||||
|
然后双击 `start.bat`,或在终端运行 `node server.js`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
启动后访问 `http://localhost:8002`,输入密码即可使用。
|
启动后访问 `http://localhost:8002`,输入密码即可使用。
|
||||||
|
|
||||||
## 配置
|
## 配置
|
||||||
@@ -106,6 +113,7 @@ cc-web/
|
|||||||
├── sessions/ # 对话历史 JSON 文件(运行时生成)
|
├── sessions/ # 对话历史 JSON 文件(运行时生成)
|
||||||
├── logs/ # 进程生命周期日志(运行时生成)
|
├── logs/ # 进程生命周期日志(运行时生成)
|
||||||
├── .env.example # 环境变量模板
|
├── .env.example # 环境变量模板
|
||||||
|
├── start.bat # Windows 一键启动脚本
|
||||||
├── .gitignore
|
├── .gitignore
|
||||||
├── package.json
|
├── package.json
|
||||||
└── README.md
|
└── README.md
|
||||||
@@ -210,6 +218,24 @@ server {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Windows 部署
|
||||||
|
|
||||||
|
适用于在个人电脑上运行 CC-Web,通过手机远程控制 Claude Code。
|
||||||
|
|
||||||
|
**启动方式**:双击 `start.bat`,或在终端运行:
|
||||||
|
```cmd
|
||||||
|
cd cc-web
|
||||||
|
npm install
|
||||||
|
node server.js
|
||||||
|
```
|
||||||
|
|
||||||
|
**局域网访问**(手机和电脑在同一 WiFi):
|
||||||
|
- 直接访问 `http://电脑局域网IP:8002`
|
||||||
|
|
||||||
|
**远程访问**(外出时用手机控制家里电脑):
|
||||||
|
- 推荐使用 [Tailscale](https://tailscale.com/) — 电脑和手机各安装一个,自动组网,免费够用
|
||||||
|
- 或使用 [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/)(需域名)
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
- **后端**:Node.js + [ws](https://github.com/websockets/ws)(唯一依赖)
|
- **后端**:Node.js + [ws](https://github.com/websockets/ws)(唯一依赖)
|
||||||
@@ -219,5 +245,5 @@ server {
|
|||||||
|
|
||||||
## 补充说明
|
## 补充说明
|
||||||
|
|
||||||
- 暂时只支持linux服务器,后续再vibe电脑端
|
- 支持 Linux 服务器和 Windows 个人电脑部署
|
||||||
- 暂时只支持claude code,后续再vibe codex
|
- 暂时只支持 Claude Code,后续再 vibe codex
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cc-web",
|
"name": "cc-web",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node server.js"
|
"start": "node server.js"
|
||||||
|
|||||||
25
server.js
25
server.js
@@ -284,6 +284,8 @@ function modelShortName(fullModel) {
|
|||||||
return entry ? entry[0] : null;
|
return entry ? entry[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const IS_WIN = process.platform === 'win32';
|
||||||
|
|
||||||
function isProcessRunning(pid) {
|
function isProcessRunning(pid) {
|
||||||
try {
|
try {
|
||||||
process.kill(pid, 0);
|
process.kill(pid, 0);
|
||||||
@@ -293,6 +295,18 @@ function isProcessRunning(pid) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function killProcess(pid, force = false) {
|
||||||
|
try {
|
||||||
|
if (IS_WIN) {
|
||||||
|
const args = ['/T', '/PID', String(pid)];
|
||||||
|
if (force) args.unshift('/F');
|
||||||
|
spawn('taskkill', args, { windowsHide: true });
|
||||||
|
} else {
|
||||||
|
process.kill(pid, force ? 'SIGKILL' : 'SIGTERM');
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
function cleanRunDir(sessionId) {
|
function cleanRunDir(sessionId) {
|
||||||
const dir = runDir(sessionId);
|
const dir = runDir(sessionId);
|
||||||
try {
|
try {
|
||||||
@@ -731,7 +745,7 @@ function handleSlashCommand(ws, text, sessionId) {
|
|||||||
if (session) {
|
if (session) {
|
||||||
if (activeProcesses.has(sessionId)) {
|
if (activeProcesses.has(sessionId)) {
|
||||||
const entry = activeProcesses.get(sessionId);
|
const entry = activeProcesses.get(sessionId);
|
||||||
try { process.kill(entry.pid, 'SIGTERM'); } catch {}
|
killProcess(entry.pid);
|
||||||
if (entry.tailer) entry.tailer.stop();
|
if (entry.tailer) entry.tailer.stop();
|
||||||
activeProcesses.delete(sessionId);
|
activeProcesses.delete(sessionId);
|
||||||
cleanRunDir(sessionId);
|
cleanRunDir(sessionId);
|
||||||
@@ -899,7 +913,7 @@ function handleLoadSession(ws, sessionId) {
|
|||||||
function handleDeleteSession(ws, sessionId) {
|
function handleDeleteSession(ws, sessionId) {
|
||||||
if (activeProcesses.has(sessionId)) {
|
if (activeProcesses.has(sessionId)) {
|
||||||
const entry = activeProcesses.get(sessionId);
|
const entry = activeProcesses.get(sessionId);
|
||||||
try { process.kill(entry.pid, 'SIGTERM'); } catch {}
|
try { killProcess(entry.pid); } catch {}
|
||||||
if (entry.tailer) entry.tailer.stop();
|
if (entry.tailer) entry.tailer.stop();
|
||||||
activeProcesses.delete(sessionId);
|
activeProcesses.delete(sessionId);
|
||||||
if (entry.ws) wsSend(entry.ws, { type: 'done', sessionId });
|
if (entry.ws) wsSend(entry.ws, { type: 'done', sessionId });
|
||||||
@@ -961,9 +975,9 @@ function handleAbort(ws) {
|
|||||||
if (!entry) return;
|
if (!entry) return;
|
||||||
|
|
||||||
plog('INFO', 'user_abort', { sessionId: sessionId.slice(0, 8), pid: entry.pid });
|
plog('INFO', 'user_abort', { sessionId: sessionId.slice(0, 8), pid: entry.pid });
|
||||||
try { process.kill(entry.pid, 'SIGTERM'); } catch {}
|
killProcess(entry.pid);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
try { process.kill(entry.pid, 'SIGKILL'); } catch {}
|
killProcess(entry.pid, true);
|
||||||
}, 3000);
|
}, 3000);
|
||||||
// handleProcessComplete will be triggered by the PID monitor
|
// handleProcessComplete will be triggered by the PID monitor
|
||||||
}
|
}
|
||||||
@@ -1061,9 +1075,10 @@ function handleMessage(ws, msg) {
|
|||||||
try {
|
try {
|
||||||
proc = spawn(CLAUDE_PATH, args, {
|
proc = spawn(CLAUDE_PATH, args, {
|
||||||
env,
|
env,
|
||||||
cwd: process.env.HOME || process.cwd(),
|
cwd: process.env.HOME || process.env.USERPROFILE || process.cwd(),
|
||||||
stdio: [inputFd, outputFd, errorFd],
|
stdio: [inputFd, outputFd, errorFd],
|
||||||
detached: true,
|
detached: true,
|
||||||
|
windowsHide: true,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
fs.closeSync(inputFd);
|
fs.closeSync(inputFd);
|
||||||
|
|||||||
20
start.bat
Normal file
20
start.bat
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001 >nul 2>&1
|
||||||
|
cd /d "%~dp0"
|
||||||
|
|
||||||
|
where node >nul 2>&1
|
||||||
|
if %errorlevel% neq 0 (
|
||||||
|
echo [ERROR] Node.js not found. Please install Node.js first.
|
||||||
|
echo https://nodejs.org/
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
if not exist node_modules (
|
||||||
|
echo Installing dependencies...
|
||||||
|
npm install
|
||||||
|
)
|
||||||
|
|
||||||
|
echo Starting CC-Web...
|
||||||
|
node server.js
|
||||||
|
pause
|
||||||
Reference in New Issue
Block a user