搭建X-UI

1. 在 cloudflare 创建 2 个域名

获取 serv00 的 IPV4

2. 开启应用权限并开放 2 个随机端口

3. 在 serv00 添加 cloudflare 域名

4. 下载 XUI

用 FinallShell 连接 SSH,建议勾选智能加速,连接时会提示输入密码,不要把对话框叉掉,点击取消再输入密码就能连接

进入 public_html 文件下

1
cd /usr/home/你的serv名字/domains/域名/public_html

下载 xui

1
git clone https://github.com/zjmkkk/xui-serv00.git && mv xui-serv00/x-ui.sh ./ && rm -rf xui-serv00

xui 项目地址:https://github.com/parentalclash/x-ui-freebsd

5. 安装 XUI

赋权

1
chmod +x /usr/home/serv00名字/domains/域名/public_html/x-ui.sh

运行 sui

1
./x-ui.sh

选 1


然后输入你的用户名密码 , 监听端口修改为前面搭建节点的端口

出现这个界面表示成功安装

6. 去 cloudflare 创建自定义规则

先将规则改为灵活

创建自定义规则


创建 xui 面板规则

创建节点规则

7. 访问 xui 面板并创建节点

访问你 xui 绑定的域名


创建节点

将节点导入 v2ray

修改节点信息

测试看已经跑起来了

8. 创建保活脚本

由于 serv00 不定期杀后台,所以需要创建一个保活脚本使 xui 稳定运行

在 public_html 目录下创建 start.sh 文本

1
2
3
4
5
6
7
#!/bin/bash

# 进入指定目录
cd /usr/home/serv00账号/domains/你的xui域名/public_html

# 运行 x-ui.sh 并自动选择 8 和 0
echo -e "8\n0" | ./x-ui.sh

在 public_html 目录下创建 restart.sh 文本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash

# 设置脚本路径
SCRIPT_PATH="/usr/home/serv00账号/domains/你的xui域名/public_html/start.sh"
WORK_DIR="/usr/home/serv00账号/domains/你的xui域名/public_html"
PORT=你的xui端口

# 检查端口是否被占用
if ! sockstat -4 -l | grep -q ":$PORT"
then
# 如果端口没有被占用,则重新启动脚本
cd "$WORK_DIR"
nohup ./start.sh > /dev/null 2>&1 &
echo "Restarted start.sh at $(date)" >> "$WORK_DIR/restart_log.txt"
fi

将上面的 serv00账号你的xui域名你的xui端口替换为自己的

赋权脚本

1
2
3
chmod +x /usr/home/serv00名字/domains/域名/public_html/start.sh

chmod +x /usr/home/serv00名字/domains/域名/public_html/restart.sh

最后将 restart.sh 路径放入 CF 保活脚本即可稳定运行。

1
sh /usr/home/serv00名字/domains/域名/public_html/restart.sh

CF 保活教程

Worker 网页中

电报中:

serv00 面板:

1. 在 cloudflare 创建 worker

登录以下网址:https://www.cloudflare.com/

创建 worker


将下面代码复制粘贴进 worker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})

addEventListener('scheduled', event => {
event.waitUntil(handleScheduled(event.scheduledTime))
})

async function handleRequest(request) {
const url = new URL(request.url)

if (url.pathname === '/login' && request.method === 'POST') {
const formData = await request.formData()
const password = formData.get('password')

if (password === PASSWORD) {
const response = new Response(JSON.stringify({ success: true }), {
headers: { 'Content-Type': 'application/json' }
})
response.headers.set('Set-Cookie', `auth=${PASSWORD}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=86400`)
return response
} else {
return new Response(JSON.stringify({ success: false }), {
headers: { 'Content-Type': 'application/json' }
})
}
} else if (url.pathname === '/run' && request.method === 'POST') {
if (!isAuthenticated(request)) {
return new Response('Unauthorized', { status: 401 })
}

await handleScheduled(new Date().toISOString())
const results = await CRON_RESULTS.get('lastResults', 'json')
return new Response(JSON.stringify(results), {
headers: { 'Content-Type': 'application/json' }
})
} else if (url.pathname === '/results' && request.method === 'GET') {
if (!isAuthenticated(request)) {
return new Response(JSON.stringify({ authenticated: false }), {
headers: { 'Content-Type': 'application/json' }
})
}
const results = await CRON_RESULTS.get('lastResults', 'json')
return new Response(JSON.stringify({ authenticated: true, results: results || [] }), {
headers: { 'Content-Type': 'application/json' }
})
} else if (url.pathname === '/check-auth' && request.method === 'GET') {
return new Response(JSON.stringify({ authenticated: isAuthenticated(request) }), {
headers: { 'Content-Type': 'application/json' }
})
} else {
// 显示登录页面或结果页面的 HTML
return new Response(getHtmlContent(), {
headers: { 'Content-Type': 'text/html' },
})
}
}

function isAuthenticated(request) {
const cookies = request.headers.get('Cookie')
if (cookies) {
const authCookie = cookies.split(';').find(c => c.trim().startsWith('auth='))
if (authCookie) {
const authValue = authCookie.split('=')[1]
return authValue === PASSWORD
}
}
return false
}

function getHtmlContent() {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Worker Control Panel</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background-color: #f0f0f0;
}
.container {
text-align: center;
padding: 20px;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
max-width: 800px;
width: 100%;
}
input, button {
margin: 10px 0;
padding: 10px;
width: 200px;
border-radius: 4px;
border: 1px solid #ddd;
}
button {
background-color: #4CAF50;
border: none;
color: white;
cursor: pointer;
}
#status {
margin-top: 20px;
font-weight: bold;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
#loginForm, #dashboard {
display: none;
}
</style>
</head>
<body>
<div class="container">
<h1>Worker Control Panel</h1>
<div id="loginForm">
<input type="password" id="password" placeholder="Enter password">
<button onclick="login()">Login</button>
</div>
<div id="dashboard">
<button onclick="runScript()">Run Script</button>
<div id="status"></div>
<table id="resultsTable">
<thead>
<tr>
<th>Account</th>
<th>Type</th>
<th>Status</th>
<th>Message</th>
<th>Last Run</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
<script>
let password = '';

function showLoginForm() {
document.getElementById('loginForm').style.display = 'block';
document.getElementById('dashboard').style.display = 'none';
}

function showDashboard() {
document.getElementById('loginForm').style.display = 'none';
document.getElementById('dashboard').style.display = 'block';
fetchResults();
}

async function checkAuth() {
const response = await fetch('/check-auth');
const data = await response.json();
if (data.authenticated) {
showDashboard();
} else {
showLoginForm();
}
}

async function login() {
password = document.getElementById('password').value;
const formData = new FormData();
formData.append('password', password);
const response = await fetch('/login', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
showDashboard();
} else {
alert('Incorrect password');
}
}

async function runScript() {
const statusDiv = document.getElementById('status');
statusDiv.textContent = 'Executing script...';
try {
const response = await fetch('/run', { method: 'POST' });
if (response.ok) {
const results = await response.json();
displayResults(results);
statusDiv.textContent = 'Script executed successfully!';
} else if (response.status === 401) {
statusDiv.textContent = 'Unauthorized. Please login again.';
showLoginForm();
} else {
statusDiv.textContent = 'Error executing script.';
}
} catch (error) {
statusDiv.textContent = 'Error: ' + error.message;
}
}

async function fetchResults() {
try {
const response = await fetch('/results');
if (response.ok) {
const data = await response.json();
if (data.authenticated) {
displayResults(data.results);
} else {
showLoginForm();
}
} else {
console.error('Failed to fetch results');
showLoginForm();
}
} catch (error) {
console.error('Error fetching results:', error);
showLoginForm();
}
}

function displayResults(results) {
const tbody = document.querySelector('#resultsTable tbody');
tbody.innerHTML = '';
results.forEach(result => {
result.cronResults.forEach((cronResult, index) => {
const row = tbody.insertRow();
if (index === 0) {
row.insertCell(0).textContent = result.username;
row.insertCell(1).textContent = result.type;
} else {
row.insertCell(0).textContent = '';
row.insertCell(1).textContent = '';
}
row.insertCell(2).textContent = cronResult.success ? 'Success' : 'Failed';
row.insertCell(3).textContent = cronResult.message;
row.insertCell(4).textContent = new Date(result.lastRun).toLocaleString();
});
});
}

// 页面加载时检查认证状态
document.addEventListener('DOMContentLoaded', checkAuth);
</script>
</body>
</html>
`;
}

async function handleScheduled(scheduledTime) {
const accountsData = JSON.parse(ACCOUNTS_JSON);
const accounts = accountsData.accounts;

let results = [];
for (const account of accounts) {
const result = await loginAccount(account);
results.push(result);
await delay(Math.floor(Math.random() * 8000) + 1000);
}

// 保存结果到 KV 存储
await CRON_RESULTS.put('lastResults', JSON.stringify(results));
}

function generateRandomUserAgent() {
const browsers = ['Chrome', 'Firefox', 'Safari', 'Edge', 'Opera'];
const browser = browsers[Math.floor(Math.random() * browsers.length)];
const version = Math.floor(Math.random() * 100) + 1;
const os = ['Windows NT 10.0', 'Macintosh', 'X11'];
const selectedOS = os[Math.floor(Math.random() * os.length)];
const osVersion = selectedOS === 'X11' ? 'Linux x86_64' : selectedOS === 'Macintosh' ? 'Intel Mac OS X 10_15_7' : 'Win64; x64';

return `Mozilla/5.0 (${selectedOS}; ${osVersion}) AppleWebKit/537.36 (KHTML, like Gecko) ${browser}/${version}.0.0.0 Safari/537.36`;
}

async function loginAccount(account) {
const { username, password, panelnum, type, cronCommands } = account
let baseUrl = type === 'ct8'
? 'https://panel.ct8.pl'
: `https://panel${panelnum}.serv00.com`
let loginUrl = `${baseUrl}/login/?next=/cron/`

const userAgent = generateRandomUserAgent();

try {
const response = await fetch(loginUrl, {
method: 'GET',
headers: {
'User-Agent': userAgent,
},
})

const pageContent = await response.text()
const csrfMatch = pageContent.match(/name="csrfmiddlewaretoken" value="([^"]*)"/)
const csrfToken = csrfMatch ? csrfMatch[1] : null

if (!csrfToken) {
throw new Error('CSRF token not found')
}

const initialCookies = response.headers.get('set-cookie') || ''

const formData = new URLSearchParams({
'username': username,
'password': password,
'csrfmiddlewaretoken': csrfToken,
'next': '/cron/'
})

const loginResponse = await fetch(loginUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Referer': loginUrl,
'User-Agent': userAgent,
'Cookie': initialCookies,
},
body: formData.toString(),
redirect: 'manual'
})

if (loginResponse.status === 302 && loginResponse.headers.get('location') === '/cron/') {
const loginCookies = loginResponse.headers.get('set-cookie') || ''
const allCookies = combineCookies(initialCookies, loginCookies)

// 访问 cron 列表页面
const cronListUrl = `${baseUrl}/cron/`
const cronListResponse = await fetch(cronListUrl, {
headers: {
'Cookie': allCookies,
'User-Agent': userAgent,
}
})
const cronListContent = await cronListResponse.text()

console.log(`Cron list URL: ${cronListUrl}`)
console.log(`Cron list response status: ${cronListResponse.status}`)
console.log(`Cron list content (first 1000 chars): ${cronListContent.substring(0, 1000)}`)

let cronResults = [];
for (const cronCommand of cronCommands) {
if (!cronListContent.includes(cronCommand)) {
// 访问添加 cron 任务页面
const addCronUrl = `${baseUrl}/cron/add`
const addCronPageResponse = await fetch(addCronUrl, {
headers: {
'Cookie': allCookies,
'User-Agent': userAgent,
'Referer': cronListUrl,
}
})
const addCronPageContent = await addCronPageResponse.text()

console.log(`Add cron page URL: ${addCronUrl}`)
console.log(`Add cron page response status: ${addCronPageResponse.status}`)
console.log(`Add cron page content (first 1000 chars): ${addCronPageContent.substring(0, 1000)}`)

const newCsrfMatch = addCronPageContent.match(/name="csrfmiddlewaretoken" value="([^"]*)"/)
const newCsrfToken = newCsrfMatch ? newCsrfMatch[1] : null

if (!newCsrfToken) {
throw new Error('New CSRF token not found for adding cron task')
}

const formData = new URLSearchParams({
'csrfmiddlewaretoken': newCsrfToken,
'spec': 'manual',
'minute_time_interval': 'on',
'minute': '15',
'hour_time_interval': 'each',
'hour': '*',
'day_time_interval': 'each',
'day': '*',
'month_time_interval': 'each',
'month': '*',
'dow_time_interval': 'each',
'dow': '*',
'command': cronCommand,
'comment': 'Auto added cron job'
})

console.log('Form data being sent:', formData.toString())

const { success, response: addCronResponse, content: addCronResponseContent } = await addCronWithRetry(addCronUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Cookie': allCookies,
'User-Agent': userAgent,
'Referer': addCronUrl,
'Origin': baseUrl,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Upgrade-Insecure-Requests': '1'
},
body: formData.toString(),
})

console.log('Full response content:', addCronResponseContent)

if (success) {
if (addCronResponseContent.includes('Cron job has been added') || addCronResponseContent.includes('Zadanie cron zostało dodane')) {
const message = `添加了新的 cron 任务:${cronCommand}`;
console.log(message);
await sendTelegramMessage(`账号 ${username} (${type}) ${message}`);
cronResults.push({ success: true, message });
} else {
// 如果响应中没有成功信息,再次检查cron列表
const checkCronListResponse = await fetch(cronListUrl, {
headers: {
'Cookie': allCookies,
'User-Agent': userAgent,
}
});
const checkCronListContent = await checkCronListResponse.text();

if (checkCronListContent.includes(cronCommand)) {
const message = `确认添加了新的 cron 任务:${cronCommand}`;
console.log(message);
await sendTelegramMessage(`账号 ${username} (${type}) ${message}`);
cronResults.push({ success: true, message });
} else {
const message = `尝试添加 cron 任务:${cronCommand},但在列表中未找到。可能添加失败。`;
console.error(message);
cronResults.push({ success: false, message });
}
}
} else {
const message = `添加 cron 任务失败:${cronCommand}`;
console.error(message);
cronResults.push({ success: false, message });
}
} else {
const message = `cron 任务已存在:${cronCommand}`;
console.log(message);
cronResults.push({ success: true, message });
}
}
return { username, type, cronResults, lastRun: new Date().toISOString() };
} else {
const message = `登录失败,未知原因。请检查账号和密码是否正确。`;
console.error(message);
return { username, type, cronResults: [{ success: false, message }], lastRun: new Date().toISOString() };
}
} catch (error) {
const message = `登录或添加 cron 任务时出现错误: ${error.message}`;
console.error(message);
return { username, type, cronResults: [{ success: false, message }], lastRun: new Date().toISOString() };
}
}

async function addCronWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
const responseContent = await response.text();
console.log(`Attempt ${i + 1} response status:`, response.status);
console.log(`Attempt ${i + 1} response content (first 1000 chars):`, responseContent.substring(0, 1000));

if (response.status === 200 || response.status === 302 || responseContent.includes('Cron job has been added') || responseContent.includes('Zadanie cron zostało dodane')) {
return { success: true, response, content: responseContent };
}
} catch (error) {
console.error(`Attempt ${i + 1} failed:`, error);
}
await delay(2000); // Wait 2 seconds before retrying
}
return { success: false };
}

function combineCookies(cookies1, cookies2) {
const cookieMap = new Map()

const parseCookies = (cookieString) => {
cookieString.split(',').forEach(cookie => {
const [fullCookie] = cookie.trim().split(';')
const [name, value] = fullCookie.split('=')
if (name && value) {
cookieMap.set(name.trim(), value.trim())
}
})
}

parseCookies(cookies1)
parseCookies(cookies2)

return Array.from(cookieMap.entries()).map(([name, value]) => `${name}=${value}`).join('; ')
}

async function sendTelegramMessage(message) {
const telegramConfig = JSON.parse(TELEGRAM_JSON)
const { telegramBotToken, telegramBotUserId } = telegramConfig
const url = `https://api.telegram.org/bot${telegramBotToken}/sendMessage`

try {
await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chat_id: telegramBotUserId,
text: message
})
})
} catch (error) {
console.error('Error sending Telegram message:', error)
}
}

function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}

2. 创建变量

变量名:ACCOUNTS_JSON

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"accounts": [
{
"username": "user1",
"password": "password1",
"type": "ct8",
"cronCommands": [
"python /usr/home/serv00name/domains/backup.py"
]
},
{
"username": "user2",
"password": "password2",
"panelnum": "2",
"type": "serv00",
"cronCommands": [
"python /usr/home/serv00name/domains/backup.py"
]
}
]
}

cronCommands 这部分是你想要添加的 cron 任务,可以是路径,也可以是程序运行的命令 (一个引号内是完整的执行命令)

变量名:TELEGRAM_JSON

1
2
3
4
{
"telegramBotToken": "YOUR_BOT_TOKEN",
"telegramBotUserId": "YOUR_USER_ID"
}

**变量名:PASSWORD**——你访问worker面板的密码

3. 添加 Cron

为了避免频繁登录执行脚本,建议各位佬友把时间填 4-12h 为宜

4. 创建一个 KV 变量

变量名:CRON_RESULTS

回到刚才创建的 worker 界面绑定 KV

5. 绑定自定义域名


访问你的 worker 网站并输入密码

点击运行按钮 Run Script 脚本就开始启动了

这个面板会记录你最后一次执行的命令结果,包括你手动执行或者是设置的定时 Cron。

当你部署在 serv00 的网站不能访问时,通过访问 worker,点击一下运行脚本就能马上执行任务了,避免了频繁的使用 cron 启动脚本登录网站执行命令

电报只有当 cron 被干掉重新建立时才会通知(运行就消息推送会消息爆炸),这个可视化面板会记录输出的最后一次运行状态算是弥补了这部分

参考文章:

详解:在 serv00 上搭建 X-UI,套 CDN 并保活 —— 墙了也能玩
详解:在 Cloudflare Worker 上保活 serv00/CT8 服务进程(带可视化面板及执行命令)