Skip to content

Commit 2f8f5c0

Browse files
committed
ZeroCatCodeRun
0 parents  commit 2f8f5c0

File tree

19 files changed

+3172
-0
lines changed

19 files changed

+3172
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
data
2+
node_modules
3+
.env

Dockerfile

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
FROM ubuntu:22.04
2+
3+
# 避免交互式提示
4+
ENV DEBIAN_FRONTEND=noninteractive
5+
6+
# 设置时区
7+
ENV TZ=Asia/Shanghai
8+
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
9+
10+
# 安装基础工具和依赖
11+
RUN apt-get update && apt-get install -y \
12+
sudo \
13+
curl \
14+
wget \
15+
git \
16+
vim \
17+
bash \
18+
build-essential \
19+
software-properties-common \
20+
apt-transport-https \
21+
ca-certificates \
22+
gnupg \
23+
lsb-release \
24+
zsh \
25+
tmux \
26+
htop \
27+
tree \
28+
jq \
29+
unzip \
30+
openssh-client \
31+
postgresql-client \
32+
redis-tools \
33+
make \
34+
gcc \
35+
g++ \
36+
libssl-dev \
37+
zlib1g-dev \
38+
libbz2-dev \
39+
libreadline-dev \
40+
libsqlite3-dev \
41+
libncursesw5-dev \
42+
xz-utils \
43+
tk-dev \
44+
libxml2-dev \
45+
libxmlsec1-dev \
46+
libffi-dev \
47+
liblzma-dev \
48+
&& rm -rf /var/lib/apt/lists/*
49+
50+
# 创建非root用户
51+
RUN useradd -m -s /bin/bash zerocat \
52+
&& echo "zerocat ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/zerocat \
53+
&& chmod 0440 /etc/sudoers.d/zerocat
54+
55+
# 设置工作目录
56+
WORKDIR /home/zerocat
57+
58+
# 安装 pyenv
59+
USER zerocat
60+
RUN curl https://pyenv.run | bash
61+
ENV PYENV_ROOT="/home/zerocat/.pyenv"
62+
ENV PATH="$PYENV_ROOT/bin:$PATH"
63+
RUN echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc && \
64+
echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc && \
65+
echo 'eval "$(pyenv init --path)"' >> ~/.bashrc && \
66+
echo 'eval "$(pyenv init -)"' >> ~/.bashrc
67+
68+
# 使用 pyenv 安装 Python 版本并安装包
69+
SHELL ["/bin/bash", "-l", "-c"]
70+
RUN pyenv install 3.8.18 && \
71+
pyenv install 3.11.8 && \
72+
pyenv global 3.11.8 && \
73+
eval "$(pyenv init -)" && \
74+
eval "$(pyenv init --path)" && \
75+
$PYENV_ROOT/versions/3.11.8/bin/python -m pip install --upgrade pip && \
76+
$PYENV_ROOT/versions/3.11.8/bin/python -m pip install \
77+
ipython \
78+
poetry \
79+
virtualenv \
80+
black \
81+
flake8 \
82+
mypy \
83+
pytest \
84+
requests
85+
86+
# 安装 nvm
87+
ENV NVM_DIR="/home/zerocat/.nvm"
88+
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash \
89+
&& . "$NVM_DIR/nvm.sh" \
90+
&& nvm install 16 \
91+
&& nvm install 18 \
92+
&& nvm alias default 18 \
93+
&& nvm use default \
94+
&& npm install -g yarn pnpm typescript ts-node
95+
96+
# 设置权限
97+
USER root
98+
RUN chown -R zerocat:zerocat /home/zerocat
99+
100+
# 切换到非root用户
101+
USER zerocat
102+
103+
# 设置SHELL环境变量
104+
ENV SHELL=/bin/bash
105+
106+
# 配置终端
107+
RUN echo 'export PS1="\[\e[01;32m\]\u@\h\[\e[0m\]:\[\e[01;34m\]\w\[\e[0m\]\$ "' >> ~/.bashrc
108+
109+
# 添加环境变量到 .bashrc
110+
RUN echo 'export NVM_DIR="$HOME/.nvm"' >> ~/.bashrc \
111+
&& echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> ~/.bashrc \
112+
&& echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"' >> ~/.bashrc
113+
114+
# 设置容器启动命令
115+
CMD ["/bin/bash", "-l"]

app.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
const express = require('express');
2+
const path = require('path');
3+
const cookieParser = require('cookie-parser');
4+
const logger = require('morgan');
5+
const WebSocket = require('ws');
6+
const terminalService = require('./services/terminal');
7+
const adminService = require('./services/admin');
8+
const { validateToken } = require('./middleware/auth');
9+
const jwt = require('jsonwebtoken');
10+
const config = require('./config');
11+
const bodyParser = require('body-parser');
12+
13+
const indexRouter = require('./routes/index');
14+
const usersRouter = require('./routes/users');
15+
const terminalRouter = require('./routes/terminal');
16+
const adminRouter = require('./routes/admin');
17+
18+
const app = express();
19+
20+
// view engine setup
21+
app.set('views', path.join(__dirname, 'views'));
22+
app.set('view engine', 'ejs');
23+
24+
// middleware setup
25+
app.use(logger(config.logging.format));
26+
app.use(express.json());
27+
app.use(express.urlencoded({ extended: false }));
28+
app.use(cookieParser(config.security.cookieSecret));
29+
app.use(express.static(path.join(__dirname, 'public')));
30+
app.use(bodyParser.urlencoded({ limit: "100mb", extended: false }));
31+
app.use(bodyParser.json({ limit: "100mb" }));
32+
app.use(bodyParser.text({ limit: "100mb" }));
33+
app.use(bodyParser.raw({ limit: "100mb" }));
34+
35+
// Security headers
36+
app.use((req, res, next) => {
37+
res.set({
38+
'X-Content-Type-Options': 'nosniff',
39+
'X-Frame-Options': 'DENY',
40+
'X-XSS-Protection': '1; mode=block'
41+
});
42+
next();
43+
});
44+
45+
// Routes
46+
app.use('/', indexRouter);
47+
app.use('/users', usersRouter);
48+
app.use('/terminal', terminalRouter);
49+
app.use('/admin', adminRouter);
50+
51+
// WebSocket server setup
52+
const wss = new WebSocket.Server({ noServer: true });
53+
54+
// WebSocket authentication middleware
55+
async function authenticateWebSocket(request, socket, head) {
56+
try {
57+
// 从URL参数中获取token
58+
const url = new URL(request.url, `http://${request.headers.host}`);
59+
const token = url.searchParams.get('token');
60+
61+
if (!token) {
62+
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
63+
socket.destroy();
64+
return;
65+
}
66+
67+
// 验证token并解码用户信息
68+
const decoded = jwt.verify(token, config.jwt.secret);
69+
request.user = {
70+
userid: decoded.userid,
71+
fingerprint: decoded.fingerprint
72+
};
73+
74+
// 升级连接
75+
wss.handleUpgrade(request, socket, head, (ws) => {
76+
wss.emit('connection', ws, request);
77+
});
78+
} catch (error) {
79+
console.error('WebSocket authentication failed:', error);
80+
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
81+
socket.destroy();
82+
}
83+
}
84+
85+
// Handle WebSocket connections
86+
wss.on('connection', (ws, request) => {
87+
try {
88+
terminalService.handleConnection(ws, request);
89+
} catch (error) {
90+
console.error('Failed to handle WebSocket connection:', error);
91+
ws.close(1008, 'Authentication failed');
92+
}
93+
});
94+
95+
// Error handler
96+
app.use(function(err, req, res, next) {
97+
console.error(err.stack);
98+
res.status(err.status || 500);
99+
res.render('error', {
100+
message: err.message,
101+
error: req.app.get('env') === 'development' ? err : {}
102+
});
103+
});
104+
105+
module.exports = { app, authenticateWebSocket };

bin/www

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Module dependencies.
5+
*/
6+
7+
const { app, authenticateWebSocket } = require('../app');
8+
const debug = require('debug')('coderun:server');
9+
const http = require('http');
10+
11+
/**
12+
* Get port from environment and store in Express.
13+
*/
14+
15+
const port = normalizePort(process.env.PORT || '3033');
16+
app.set('port', port);
17+
18+
/**
19+
* Create HTTP server.
20+
*/
21+
22+
const server = http.createServer(app);
23+
24+
/**
25+
* Handle WebSocket upgrade requests
26+
*/
27+
server.on('upgrade', (request, socket, head) => {
28+
const pathname = new URL(request.url, `http://${request.headers.host}`).pathname;
29+
30+
if (pathname === '/terminal') {
31+
authenticateWebSocket(request, socket, head);
32+
} else {
33+
socket.destroy();
34+
}
35+
});
36+
37+
/**
38+
* Listen on provided port, on all network interfaces.
39+
*/
40+
41+
server.listen(port);
42+
server.on('error', onError);
43+
server.on('listening', onListening);
44+
45+
/**
46+
* Normalize a port into a number, string, or false.
47+
*/
48+
49+
function normalizePort(val) {
50+
const port = parseInt(val, 10);
51+
52+
if (isNaN(port)) {
53+
// named pipe
54+
return val;
55+
}
56+
57+
if (port >= 0) {
58+
// port number
59+
return port;
60+
}
61+
62+
return false;
63+
}
64+
65+
/**
66+
* Event listener for HTTP server "error" event.
67+
*/
68+
69+
function onError(error) {
70+
if (error.syscall !== 'listen') {
71+
throw error;
72+
}
73+
74+
const bind = typeof port === 'string'
75+
? 'Pipe ' + port
76+
: 'Port ' + port;
77+
78+
// handle specific listen errors with friendly messages
79+
switch (error.code) {
80+
case 'EACCES':
81+
console.error(bind + ' requires elevated privileges');
82+
process.exit(1);
83+
break;
84+
case 'EADDRINUSE':
85+
console.error(bind + ' is already in use');
86+
process.exit(1);
87+
break;
88+
default:
89+
throw error;
90+
}
91+
}
92+
93+
/**
94+
* Event listener for HTTP server "listening" event.
95+
*/
96+
97+
function onListening() {
98+
const addr = server.address();
99+
const bind = typeof addr === 'string'
100+
? 'pipe ' + addr
101+
: 'port ' + addr.port;
102+
debug('Listening on ' + bind);
103+
console.log(`Server running on port ${port}`);
104+
}

0 commit comments

Comments
 (0)