最近参加了一个项目开发,程序没办法在本地启动调试。所有人要进行debug需要远程连接到一个公共的环境。
但有人在debug,就会影响其它人使用。
今天感觉系统卡住了,应该是又有人在debug,但群里喊了一声,没人承认。所以我决定登录到服务器,看能不能查出谁在debug。
原理很简单,远程debug是需要通过tcp服务连接到服务器ip的,如果有人在debug,我应该可以通过端口连接信息查出对方的ip。
登录上系统后,使用netstat -anp | grep 远程debug端口,本来想着肯定能看到ESTABLISHED字样的连接,结果发现没有,只有LISTEN的进程
tcp 0 0 0.0.0.0:5381 0.0.0.0:* LISTEN 2047419/docker-prox
tcp6 0 0 :::5381 :::* LISTEN 2047431/docker-prox
理论上只要有客户端连接上来,应该就能看到ESTABLISHED的连接呀。我觉得有些奇怪,就问ai,ai说可能是因为我的java服务部署在容器,由于网络栈隔离原因,我在宿主机上看不到这个客户端连接信息。
按ai指导,我登录到容器里面,再查,发现还真是这样。在容器里面可以看到客户端的连接信息,在容器外面看不到。
我一直以为docker的网络只是宿主机的一部分,如果宿主机之外有第三方机器连接进来,宿主机应该能看到呢。
不过有一些容器里面没有netsta,为了方便检查我让豆包给我写了一个脚本
parallel_check_java_debug.sh
#!/bin/bash
# 功能:检查Java远程Debug服务,先收集所有进程再分成4组并行处理
# 特点:进程列表全量收集后分组,每组由1个进程处理;严格使用预生成临时文件
# 依赖:需要docker命令执行权限,部分操作可能需要root权限
# 1. 预生成所有临时文件(先生成文件名再使用)
ALL_PROCESSES_FILE=$(mktemp /tmp/java_all_processes.XXXXXX) # 存储所有进程列表
GROUP_FILE_1=$(mktemp /tmp/java_group_1.XXXXXX) # 第1组进程
GROUP_FILE_2=$(mktemp /tmp/java_group_2.XXXXXX) # 第2组进程
GROUP_FILE_3=$(mktemp /tmp/java_group_3.XXXXXX) # 第3组进程
GROUP_FILE_4=$(mktemp /tmp/java_group_4.XXXXXX) # 第4组进程
RESULT_FILE_1=$(mktemp /tmp/java_result_1.XXXXXX) # 第1组结果
RESULT_FILE_2=$(mktemp /tmp/java_result_2.XXXXXX) # 第2组结果
RESULT_FILE_3=$(mktemp /tmp/java_result_3.XXXXXX) # 第3组结果
RESULT_FILE_4=$(mktemp /tmp/java_result_4.XXXXXX) # 第4组结果
# 脚本退出时清理所有临时文件
trap 'rm -f "$ALL_PROCESSES_FILE" "$GROUP_FILE_1" "$GROUP_FILE_2" "$GROUP_FILE_3" "$GROUP_FILE_4" "$RESULT_FILE_1" "$RESULT_FILE_2" "$RESULT_FILE_3" "$RESULT_FILE_4"' EXIT
# 函数:检查命令是否存在
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# 函数:转换十六进制IP到十进制
hex_to_ip() {
local hex=$1
if [[ $hex =~ ^[0-9A-Fa-f]{8}$ ]]; then
printf "%d.%d.%d.%d" \
0x${hex:6:2} \
0x${hex:4:2} \
0x${hex:2:2} \
0x${hex:0:2}
return 0
else
echo "无效IP"
return 1
fi
}
# 函数:处理一组进程(从文件读取),结果写入指定文件
process_group() {
local group_file=$1 # 输入:包含本组进程列表的文件
local result_file=$2 # 输出:本组处理结果的文件
# 遍历本组所有进程
while IFS= read -r line; do
local pid=$(echo "$line" | awk '{print $2}')
local cmd_line=$(echo "$line" | awk '{$1=$2=""; print $0}')
local debug_port=$(echo "$cmd_line" | grep -oP 'address=\K\d+')
local user=$(echo "$line" | awk '{print $1}')
# 获取容器ID
local cgroup_path="/proc/$pid/cgroup"
local container_id=""
if [ -f "$cgroup_path" ]; then
container_id=$(grep -oP -- 'docker/[^/]+' "$cgroup_path" | cut -d '/' -f2 | head -n1)
if [ -z "$container_id" ]; then
container_id=$(grep -oP -- 'containerd/[^/]+' "$cgroup_path" | cut -d '/' -f2 | head -n1 | cut -c 1-12)
fi
fi
# 获取容器名称
local container_name="N/A"
if [ -n "$container_id" ]; then
container_name=$(docker inspect --format '{{.Name}}' "$container_id" 2>/dev/null | sed 's/^\///')
[ -z "$container_name" ] && container_name="未知容器($container_id)"
fi
# 检查连接
local connections=""
if [ -n "$debug_port" ]; then
if [ -n "$container_id" ]; then
# 优先使用netstat
local has_netstat=$(docker exec "$container_id" sh -c "command -v netstat >/dev/null 2>&1 && echo 1 || echo 0")
if [ "$has_netstat" -eq 1 ]; then
connections=$(docker exec "$container_id" netstat -anp 2>/dev/null | grep ":$debug_port" | grep ESTABLISHED)
else
# 其次使用ss
local has_ss=$(docker exec "$container_id" sh -c "command -v ss >/dev/null 2>&1 && echo 1 || echo 0")
if [ "$has_ss" -eq 1 ]; then
connections=$(docker exec "$container_id" ss -antp 2>/dev/null | grep ":$debug_port" | grep ESTAB)
else
# 最后解析/proc/net/tcp
local hex_port=$(printf "%04X" "$debug_port")
local proc_tcp=$(docker exec "$container_id" cat /proc/net/tcp 2>/dev/null | grep ":$hex_port" | grep " 01 ")
if [ -n "$proc_tcp" ]; then
while read -r tcp_line; do
local fields=($tcp_line)
local remote_addr=${fields[2]} # 远程地址在第3字段
local client_hex_ip=$(echo "$remote_addr" | cut -d: -f1)
local client_ip=$(hex_to_ip "$client_hex_ip")
if [ "$client_ip" != "无效IP" ]; then
connections+="ESTABLISHED $client_ip:$debug_port"#39;\n'
fi
done <<< "$proc_tcp"
fi
fi
fi
else
# 主机上的检查
if command_exists netstat; then
connections=$(netstat -anp 2>/dev/null | grep ":$debug_port" | grep ESTABLISHED)
elif command_exists ss; then
connections=$(ss -antp 2>/dev/null | grep ":$debug_port" | grep ESTAB)
fi
fi
fi
# 有连接才写入结果文件
if [ -n "$connections" ]; then
{
echo "进程ID: $pid"
echo "运行用户: $user"
echo "容器名称: $container_name"
echo "容器ID: ${container_id:-N/A}"
echo "Debug端口: $debug_port"
echo "启动命令片段: $(echo "$cmd_line" | cut -c 1-150)..."
echo "远程连接状态:"
echo " 发现以下远程连接:"
local client_ips=$(echo "$connections" | awk '{
if ($1 == "ESTABLISHED") print $2;
else if ($5 ~ /:/) print $5;
else print $4;
}' | cut -d: -f1 | sort | uniq | grep -v '^#39;)
for ip in $client_ips; do
echo " - 客户端IP: $ip"
done
echo "======================================================================"
} >> "$result_file" # 追加到本组结果文件
fi
done < "$group_file"
}
# 主逻辑:收集所有进程并分组处理
echo "正在查找启用了远程Debug的Java服务..."
ps -ef | grep 'agentlib:jdwp=' | grep -v grep > "$ALL_PROCESSES_FILE"
# 检查是否有进程
if [ ! -s "$ALL_PROCESSES_FILE" ]; then
echo "未找到启用远程Debug的Java服务"
exit 0
fi
# 计算总进程数和每组进程数
total_processes=$(wc -l < "$ALL_PROCESSES_FILE")
group_size=$(( (total_processes + 3) / 4 )) # 向上取整,确保分配均匀
# 分割进程列表到4个组文件(不足4组时自动为空)
split -l "$group_size" "$ALL_PROCESSES_FILE" -d -a 1 "$GROUP_FILE_1"_ # 临时中间文件
mv "$GROUP_FILE_1"_0 "$GROUP_FILE_1" 2>/dev/null || true
mv "$GROUP_FILE_1"_1 "$GROUP_FILE_2" 2>/dev/null || true
mv "$GROUP_FILE_1"_2 "$GROUP_FILE_3" 2>/dev/null || true
mv "$GROUP_FILE_1"_3 "$GROUP_FILE_4" 2>/dev/null || true
echo "找到以下有远程Debug连接的Java服务(总进程数: $total_processes,分组处理):"
echo "======================================================================"
# 启动分组处理(根据实际进程数决定启动几个后台任务)
bg_pids=()
if [ -s "$GROUP_FILE_1" ]; then
process_group "$GROUP_FILE_1" "$RESULT_FILE_1" &
bg_pids+=($!)
fi
if [ -s "$GROUP_FILE_2" ]; then
process_group "$GROUP_FILE_2" "$RESULT_FILE_2" &
bg_pids+=($!)
fi
if [ -s "$GROUP_FILE_3" ]; then
process_group "$GROUP_FILE_3" "$RESULT_FILE_3" &
bg_pids+=($!)
fi
if [ -s "$GROUP_FILE_4" ]; then
process_group "$GROUP_FILE_4" "$RESULT_FILE_4" &
bg_pids+=($!)
fi
# 等待所有后台任务完成
if [ ${#bg_pids[@]} -gt 0 ]; then
wait "${bg_pids[@]}"
fi
# 按分组顺序合并结果(保持原始进程顺序)
cat "$RESULT_FILE_1" "$RESULT_FILE_2" "$RESULT_FILE_3" "$RESULT_FILE_4"
echo "检查完成"
写完脚本,考虑到不是所有人都有权限登录到后台服务机器上执行,我又让豆包帮我写了一个python脚本,可以在网页上执行脚本,并查看脚本输出结果
web_shell_invoker.py
# -*- coding: utf-8 -*-
from flask import Flask, request, jsonify, send_from_directory
import subprocess
import os
import sys
import argparse
# 获取当前脚本所在目录
APP_ROOT = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, static_folder=APP_ROOT)
# 配置不同接口对应的shell脚本路径
SHELL_SCRIPTS = {
"system-debug-info": "/root/hch/bin/parallel_check_java_debug.sh", # 检查有哪些java服务正在被debug
"disk-usage": "/usr/local/bin/disk_usage.sh", # 示例:磁盘使用脚本
"custom-script": "/path/to/your/custom_script.sh" # 自定义脚本
}
def run_shell_script(script_path, args=None):
"""执行shell脚本并返回结果"""
# 检查脚本是否存在
if not os.path.exists(script_path):
return {
"success": False,
"error": f"脚本不存在: {script_path}",
"output": ""
}
# 检查是否有执行权限
if not os.access(script_path, os.X_OK):
return {
"success": False,
"error": f"没有执行权限: {script_path}",
"output": ""
}
try:
# 构建命令列表
cmd = [script_path]
if args:
cmd.extend(args)
# 执行命令
result = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
timeout=30 # 超时时间30秒
)
# 检查执行结果
if result.returncode != 0:
return {
"success": False,
"error": f"脚本执行出错: {result.stderr}",
"output": result.stdout
}
return {
"success": True,
"error": "",
"output": result.stdout
}
except subprocess.TimeoutExpired:
return {
"success": False,
"error": "脚本执行超时",
"output": ""
}
except Exception as e:
return {
"success": False,
"error": f"执行过程出错: {str(e)}",
"output": ""
}
# 示例接口:带参数的文件查询接口
@app.route('/file-query', methods=['GET'])
def file_query():
"""查询指定目录下的文件"""
path = request.args.get('path', '/')
pattern = request.args.get('pattern', '*')
return jsonify(run_shell_script('/bin/ls', [f'--color=never', '-l', f'{path}/{pattern}']))
# 系统信息接口
@app.route('/system-debug-info', methods=['GET'])
def system_info():
return jsonify(run_shell_script(SHELL_SCRIPTS["system-debug-info"]))
# 磁盘使用接口
@app.route('/disk-usage', methods=['GET'])
def disk_usage():
path = request.args.get('path', '/')
return jsonify(run_shell_script(SHELL_SCRIPTS["disk-usage"], [path]))
# 自定义脚本接口
@app.route('/custom-script', methods=['GET'])
def custom_script():
args = [f"{k}={v}" for k, v in request.args.items()]
return jsonify(run_shell_script(SHELL_SCRIPTS["custom-script"], args))
# 首页:返回同目录下的index.html
@app.route('/', methods=['GET'])
def index():
return send_from_directory(APP_ROOT, 'index.html')
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Web接口调用Shell脚本服务')
parser.add_argument('--port', type=int, default=5555, help='服务端口号,默认5555')
args = parser.parse_args()
app.run(host='0.0.0.0', port=args.port, debug=False)
脚本配套的网页(需要放在python脚本同目录下)
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shell脚本调用服务</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f5f7fa;
min-height: 100vh;
}
.app-container {
display: flex;
max-width: 1400px;
margin: 0 auto;
padding: 20px;
gap: 20px;
height: calc(100vh - 40px);
}
header {
background-color: #2c3e50;
color: white;
padding: 15px 20px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
header h1 {
font-size: 1.8rem;
font-weight: 600;
}
/* 左侧脚本列表 */
.script-list {
width: 300px;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
overflow: hidden;
display: flex;
flex-direction: column;
}
.list-header {
padding: 15px 20px;
background-color: #34495e;
color: white;
font-size: 1.1rem;
font-weight: 500;
}
.script-items {
list-style: none;
overflow-y: auto;
flex-grow: 1;
}
.script-item {
padding: 15px 20px;
border-bottom: 1px solid #f1f1f1;
cursor: pointer;
transition: background-color 0.2s;
}
.script-item:hover {
background-color: #f8f9fa;
}
.script-item.active {
background-color: #e3f2fd;
border-left: 4px solid #2196f3;
}
.script-item h3 {
font-size: 1rem;
margin-bottom: 5px;
color: #2c3e50;
}
.script-item p {
font-size: 0.85rem;
color: #7f8c8d;
}
/* 右侧内容区 */
.content-area {
flex-grow: 1;
display: flex;
flex-direction: column;
gap: 20px;
}
/* 参数输入区 */
.param-input {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
padding: 20px;
}
.param-input h2 {
color: #2c3e50;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
font-size: 1.4rem;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 6px;
font-weight: 500;
color: #34495e;
}
input {
width: 100%;
padding: 10px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
transition: border-color 0.2s;
}
input:focus {
outline: none;
border-color: #2196f3;
box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1);
}
button {
background-color: #2196f3;
color: white;
border: none;
border-radius: 4px;
padding: 10px 20px;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s;
}
button:hover {
background-color: #1976d2;
}
button:disabled {
background-color: #bdbdbd;
cursor: not-allowed;
}
/* 结果展示区 */
.result-display {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
padding: 20px;
flex-grow: 1;
display: flex;
flex-direction: column;
}
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.result-header h2 {
color: #2c3e50;
font-size: 1.4rem;
}
.result-timestamp {
font-size: 0.85rem;
color: #7f8c8d;
}
.result-content {
flex-grow: 1;
overflow-y: auto;
padding: 15px;
border-radius: 4px;
background-color: #f8f9fa;
font-family: 'Consolas', 'Monaco', monospace;
white-space: pre-wrap;
line-height: 1.5;
}
.result-content.loading {
color: #7f8c8d;
}
.result-content.success {
background-color: #f1f8e9;
border: 1px solid #dcedc8;
}
.result-content.error {
background-color: #ffebee;
border: 1px solid #ffcdd2;
}
/* 响应式调整 */
@media (max-width: 900px) {
.app-container {
flex-direction: column;
height: auto;
}
.script-list {
width: 100%;
max-height: 300px;
}
}
</style>
</head>
<body>
<header>
<h1>Shell脚本调用服务</h1>
</header>
<div class="app-container">
<!-- 左侧脚本列表 -->
<div class="script-list">
<div class="list-header">可用脚本</div>
<ul class="script-items">
<!-- li class="script-item active" data-script="file-query">
<h3>文件查询</h3>
<p>/file-query</p>
<p>查询指定目录下的文件</p>
</li>
<li class="script-item" data-script="disk-usage">
<h3>磁盘使用</h3>
<p>/disk-usage</p>
<p>查看指定路径的磁盘使用情况</p>
</li -->
<li class="script-item active" data-script="system-debug-info">
<h3>debug信息</h3>
<p>/system-debug-info</p>
<p>获取正在debug的java服务</p>
</li>
<!-- li class="script-item" data-script="custom-script">
<h3>自定义脚本</h3>
<p>/custom-script</p>
<p>执行自定义脚本并传递参数</p>
</li -->
</ul>
</div>
<!-- 右侧内容区 -->
<div class="content-area">
<!-- 参数输入区 -->
<div class="param-input">
<h2 id="currentScriptTitle">debug信息 (/system-debug-info)</h2>
<!-- 文件查询表单 -->
<div id="file-query-form" class="script-form" style="display: none;">
<div class="form-group">
<label for="path">目录路径:</label>
<input type="text" id="path" name="path" value="/tmp" placeholder="例如:/tmp">
</div>
<div class="form-group">
<label for="pattern">文件匹配模式:</label>
<input type="text" id="pattern" name="pattern" value="*.log" placeholder="例如:*.log 或 *">
</div>
<button type="button" onclick="executeScript('file-query')">执行查询</button>
</div>
<!-- 磁盘使用表单 -->
<div id="disk-usage-form" class="script-form" style="display: none;">
<div class="form-group">
<label for="diskPath">查询路径:</label>
<input type="text" id="diskPath" name="path" value="/" placeholder="例如:/">
</div>
<button type="button" onclick="executeScript('disk-usage')">查询磁盘使用</button>
</div>
<!-- debug信息表单 -->
<div id="system-debug-info-form" class="script-form">
<p>点击下方按钮检查有哪些java服务正在被debug</p>
<button type="button" onclick="executeScript('system-debug-info')">开始检查</button>
</div>
<!-- 自定义脚本表单 -->
<div id="custom-script-form" class="script-form" style="display: none;">
<div class="form-group">
<label for="param1">参数1:</label>
<input type="text" id="param1" name="param1" placeholder="参数1名称=值">
</div>
<div class="form-group">
<label for="param2">参数2:</label>
<input type="text" id="param2" name="param2" placeholder="参数2名称=值">
</div>
<button type="button" onclick="executeScript('custom-script')">执行自定义脚本</button>
</div>
</div>
<!-- 结果展示区 -->
<div class="result-display">
<div class="result-header">
<h2>执行结果</h2>
<div class="result-timestamp" id="resultTimestamp">-</div>
</div>
<div class="result-content" id="resultContent">
请选择左侧的脚本,在上方填写参数并点击执行按钮
</div>
</div>
</div>
</div>
<script>
// 脚本信息映射
const scriptInfo = {
"file-query": {
title: "文件查询 (/file-query)",
description: "查询指定目录下的文件"
},
"disk-usage": {
title: "磁盘使用 (/disk-usage)",
description: "查看指定路径的磁盘使用情况"
},
"system-debug-info": {
title: "debug信息 (/system-debug-info)",
description: "获取系统正在被debug的java服务"
},
"custom-script": {
title: "自定义脚本 (/custom-script)",
description: "执行自定义脚本并传递参数"
}
};
// 切换脚本
document.querySelectorAll('.script-item').forEach(item => {
item.addEventListener('click', function() {
// 移除所有active类
document.querySelectorAll('.script-item').forEach(i => i.classList.remove('active'));
// 给当前点击项添加active类
this.classList.add('active');
// 获取脚本名称
const scriptName = this.getAttribute('data-script');
// 更新标题
document.getElementById('currentScriptTitle').textContent = scriptInfo[scriptName].title;
// 隐藏所有表单
document.querySelectorAll('.script-form').forEach(form => {
form.style.display = 'none';
});
// 显示当前脚本对应的表单
document.getElementById(`${scriptName}-form`).style.display = 'block';
});
});
// 获取当前时间格式化字符串
function getCurrentTime() {
const now = new Date();
return now.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
}).replace(',', ' ');
}
// 执行脚本
async function executeScript(scriptName) {
const resultContent = document.getElementById('resultContent');
const resultTimestamp = document.getElementById('resultTimestamp');
let url = `/${scriptName}`;
let params = {};
// 根据脚本类型收集参数
switch(scriptName) {
case 'file-query':
params = {
path: document.getElementById('path').value,
pattern: document.getElementById('pattern').value
};
break;
case 'disk-usage':
params = {
path: document.getElementById('diskPath').value
};
break;
case 'system-debug-info':
// 无参数
break;
case 'custom-script':
if (document.getElementById('param1').value) {
const [key, value] = document.getElementById('param1').value.split('=');
if (key) params[key] = value || '';
}
if (document.getElementById('param2').value) {
const [key, value] = document.getElementById('param2').value.split('=');
if (key) params[key] = value || '';
}
break;
}
// 显示加载状态
resultContent.textContent = "正在执行...";
resultContent.className = "result-content loading";
resultTimestamp.textContent = "执行中...";
try {
// 构建带参数的URL
const queryString = new URLSearchParams(params).toString();
if (queryString) url += `?${queryString}`;
// 发送请求
const response = await fetch(url);
const data = await response.json();
// 更新时间戳
resultTimestamp.textContent = `更新时间: ${getCurrentTime()}`;
// 显示结果
if (data.success) {
resultContent.textContent = data.output || "执行成功,无返回内容";
resultContent.className = "result-content success";
} else {
resultContent.textContent = `错误:${data.error}\n输出:${data.output}`;
resultContent.className = "result-content error";
}
} catch (error) {
resultTimestamp.textContent = `更新时间: ${getCurrentTime()}`;
resultContent.textContent = `请求失败:${error.message}`;
resultContent.className = "result-content error";
}
}
</script>
</body>
</html>
运行说明:
版权声明
本项目的版权归原作者 [皇家救星] 所有 。在项目存续期间,作者保留对代码及相关文档的版权权益。
开源协议核心条款
- 使用与分发:您有权免费使用本项目代码,无论是个人学习、内部业务应用还是商业用途。同时,您可以将本项目的副本进行分发,但需完整保留项目中的版权声明及开源协议文件。例如,如果您在自己的项目中引用了本项目的部分代码,在您项目的相关文件中应清晰注明代码来源及本项目的开源协议。
- 修改权限:您可以对本项目代码进行修改以满足自身需求。但修改后的代码在分发时,同样需要遵循本开源协议。若您对代码进行了重大修改,建议在相关文件头部或说明文档中,简要描述修改的内容及目的。
- 专利与商标:本开源协议不涉及对专利及商标使用的额外授权。在使用本项目过程中,如涉及专利及商标相关问题,需遵循相关法律法规及第三方权利声明。
- 免责声明:本项目代码按 “原样” 提供,作者不提供任何形式的明示或暗示的保证,包括但不限于适销性、特定用途适用性和非侵权保证。在任何情况下,作者对因使用本项目代码所产生的任何直接、间接、偶然、特殊或后果性损害概不负责。
安装说明
1 首先安装Miniconda,后面需要依赖它创建python环境
mkdir -p ~/hch/src
cd ~/hch/src
# 下载最新 Miniconda3(适用于 Python 3)
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
# 运行安装脚本,指定安装路径(例如 /opt/miniconda3,避免权限问题)
bash Miniconda3-latest-Linux-x86_64.sh
(执行后按提示安装)
# 初始化环境(可选,让终端能识别 conda 命令)
/opt/miniconda3/bin/conda init
source ~/.bash_profile
# 关闭base显示
conda config --set auto_activate_base false
2 放置项目文件
mkdir -p ~/hch/web_shell_invoker
上传web_shell_invoker.python和index.html
mkdir -p ~/hch/bin
上传parallel_check_java_debug.sh
chmod +x ~/hch/bin/parallel_check_java_debug.sh
3 创建python运行环境
# 接受main和r两个仓库的条款
conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main
conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r
cd ~/hch/web_shell_invoker
conda create -n web_shell_env python=3.9 -y
conda activate web_shell_env
# 安装脚本所需的 flask
pip install flask
4 开启程序
# 放开访问端口
sudo iptables -A INPUT -p tcp --dport 5555 -j ACCEPT
# service iptables save
sudo firewall-cmd --zone=public --add-port=5555/tcp --permanent
sudo firewall-cmd --reload
# 创建运行环境
cd ~/hch/web_shell_invoker
conda activate web_shell_env
# 运行脚本
nohup python web_shell_invoker.py &